Textbox binding to list won't update - wpf

HI,
I have a list of coursesTaken with dates (returned by a nested class). Since I am not sure howmany courses the person took, I just bind it to textboxes and is working fine. The datacontext of the grid is the list itself so I assume whenever I make changes to the form, it is automatically sent back to the list and add it, but when it is time to save it in DB, the list won't change. I tried making the list as an observable collection and bind mode two way but still won't change. when I try to put Onpropertychange it says "Cannot acces a non static member of outer type via nested type..." What I want to do is whenever I type / add something to textbox, the list will add it as it's nth item so I can iterate and save it in DB.
is ther any other way to do this? please see code below. thanks!
#region class for binding purposes (nested class)
public class ListCon
{
public ObservableCollection<tblGALContinuingEducationHistory> EducList
{
get
{
return new ObservableCollection<tblGALContinuingEducationHistory>(contEducHstoryList);
}
}
}
#endregion
#region constructor
public ContinuingEducHistoryPopUp(tblAttorneyGalFileMaint currentGal)
{
contEducHstoryList = new ObservableCollection<tblGALContinuingEducationHistory>();
InitializeComponent();
if (currentGal != null)
{
CurrentGal = currentGal;
txtMemberName.Text = CurrentGal.FullName;
contEducHstoryList = new ObservableCollection<tblGALContinuingEducationHistory>(currentGal.tblGALContinuingEducationHistories.Count > 0 && currentGal.tblGALContinuingEducationHistories != null ? currentGal.tblGALContinuingEducationHistories : null);
}
this.DataContext = new ListCon();
}
#endregion
Now here's my xaml
<Grid Grid.Row="2" Grid.ColumnSpan="2" Name="grdEducForm">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path=EducList[0].CourseTitle}" Grid.Column="0" Grid.Row="0" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[0].CertificateDate}" Grid.Column="1" Grid.Row="0" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[0].CertificateExpiration}" Grid.Column="2" Grid.Row="0" />
<TextBox Text="{Binding EducList[1].CourseTitle}" Grid.Column="0" Grid.Row="1" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[1].CertificateDate}" Grid.Column="1" Grid.Row="1" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[1].CertificateExpiration}" Grid.Column="2" Grid.Row="1" />
<TextBox Text="{Binding Path=EducList[2].CourseTitle}" Grid.Column="0" Grid.Row="2" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[2].CertificateDate}" Grid.Column="1" Grid.Row="2" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[2].CertificateExpiration}" x:Name="dpEnd3"
Grid.Column="2" Grid.Row="2" />
<TextBox Text="{Binding Path=EducList[3].CourseTitle, Mode=TwoWay}" Grid.Column="0" Grid.Row="3" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[3].CertificateDate, Mode=TwoWay}" Grid.Column="1"
Grid.Row="3" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[3].CertificateExpiration}" Grid.Column="2" Grid.Row="3" />
<TextBox Text="{Binding Path=EducList[4].CourseTitle}" Name="txtEducName5" Grid.Column="0" Grid.Row="4" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[4].CertificateDate}" x:Name="dpStart5" Grid.Column="1"
Grid.Row="4" />
<useable:MaskedDatePicker DateValue="{Binding Path=EducList[4].CertificateExpiration}" x:Name="dpEnd5"
Grid.Column="2" Grid.Row="4" />
</Grid>

In your ListCon property you always return a new collection this defeats the point of having an ObservableCollection at all, you should store it in a field like this:
//Initialize field in constructor
private readonly ObservableCollection<tblGALContinuingEducationHistory> _ListCon;
public ObservableCollection<tblGALContinuingEducationHistory> ListCon
{
get
{
return _ListCon;
}
}

EducList = new ObservableCollection<tblGALContinuingEducationHistory>(currentGal.tblGALContinuingEducationHistories.Count > 0 && currentGal.tblGALContinuingEducationHistories != null ? currentGal.tblGALContinuingEducationHistories : null);
int count = 0;
//5 is the maximum no of course an atty can take and save in this form
count = 5 - EducList.Count;
for (int i = 0; i < count; i++)
{
galContEdu = FileMaintenanceBusiness.Instance.Create<tblGALContinuingEducationHistory>();
galContEdu.AttorneyID = currentGal.AttorneyID;
EducList.Add(galContEdu);
}
then save only the ones that has data:
foreach (var item in EducList)
{
if(!string.IsNullOrEmpty(item.CourseTitle) || !string.IsNullOrWhiteSpace(item.CourseTitle))
FileMaintenanceBusiness.Instance.SaveChanges(item, true);
}

Related

Creation of controls dynamically on a separate thread

I am developing a WPF app, to generate folders dynamically
Right now I have an issue with what happens if the user puts 10000 folders? I am trying to run my for loop in another thread
By the way, how can I limit the user to create x number of folders?
Code Behind:
public MainWindowVM() {
SaveCommand = new Command(SaveAction);
FoldersCollection = new ObservableCollection<FolderData>();
CreateCommand = new Command(CreateAction);
CreateFoldersCommand = new Command(CreateFolderAction);
}
private void SaveAction() {
}
private void CreateFolderAction() {
var dirrName = "Assets";
var workingDirectory = Environment.CurrentDirectory;
// This will get the current PROJECT directory
var projectDirectories = Directory.GetParent(workingDirectory).Parent.Parent.GetDirectories(dirrName)
.ToList();
var path = Path.GetFullPath(projectDirectories.First().FullName);
foreach (var item in FoldersCollection) {
Directory.CreateDirectory($"{path}\\{item.FolderName}");
}
}
private void CreateAction() {
for (int i = 0; i < Value; i++) {
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate {
FoldersCollection.Add(new FolderData() { FolderID = i, FolderName = string.Empty, Extenion = new List<string>() });
}));
XAML:
<WrapPanel>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="600" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110" />
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<Label Content="Number of folders: " />
<TextBox
Grid.Column="1"
VerticalAlignment="Center"
PreviewTextInput="FolderNames_count"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
<Button
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
BorderBrush="Transparent"
Command="{Binding CreateCommand}"
Content="Create"
Focusable="False" />
<ScrollViewer
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="10"
BorderBrush="Transparent"
Focusable="False"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding FoldersCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="0,10,0,0"
VerticalAlignment="Center"
FontStyle="Oblique"
Text="{Binding FolderID}" />
<syncfusion:SfTextBoxExt
Width="300"
Margin="10,10,0,0"
VerticalAlignment="Center"
Text="{Binding FolderName, UpdateSourceTrigger=PropertyChanged}"
Watermark="Folder Name" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button
Grid.Row="2"
VerticalAlignment="Center"
Command="{Binding CreateFoldersCommand}"
Content="Create structure"
Style="{StaticResource MenuItem}" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="5,0,0,0"
VerticalAlignment="Center"
Command="{Binding SaveCommand}"
Content="Save structure"
Style="{StaticResource MenuItem}" />
</Grid>
</WrapPanel>
The app works fine, but when I exit, I get this error
btw I am doing this to allow numbers in my textbox
private void FolderNames_count(object sender, TextCompositionEventArgs e) {
if (!char.IsDigit(e.Text, e.Text.Length - 1))
e.Handled = true;
}
If you commands run on separate thread, then it looks like the bg thread still running during GUI thread is finished.
My recommendation is to put the handling of this case to the Window.Closing handler, i.e. do notify thread to break the loop, e.g. set cancellation token.
The brutal option would be to call Enviroment.Exit(0) method in the Window.Closing or Window.Closed handler, to terminate all threads. See Environment.Exit(Int32) Method
It's the loop should be executed on a background thread:
private void CreateAction()
{
Task.Run(() =>
{
for (int i = 0; i < Value; i++)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
FoldersCollection.Add(new FolderData() { FolderID = i })));
}
});
}

How to bind XamDataGrids available on different panels with different collections on a WPF page, using pure MVVM concepts & dependency injection.

I have a WPF XAML page, having 3 sections separated by DockPanels. One panel contains an INFRAGITICS XamDataGrid control to be bound with a collection.
I would like to bind XamDataGrid control using DataContext/DataSource property in pure MVVM way.
Also, would be delighted to understand if binding is done through dependency injection.
I have tried different approaches but didn't get success. I have pasted my code below for reference. Kindly help.
XAML Page:
<Window x:Class="UserInterface.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UserInterface"
xmlns:igDP="clr-namespace:Infragistics.Windows.DataPresenter;assembly=InfragisticsWPF.DataPresenter"
xmlns:igEditors="clr-namespace:Infragistics.Windows.Editors;assembly=InfragisticsWPF.Editors"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:dc ="clr-namespace:UserInterface.ViewModel"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
Title="MainWindow">
<Window.Resources>
<dc:GraphicViewModel x:Key="dataContext"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".5*"/>
<RowDefinition Height=".5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
<!--<StackPanel Orientation="Vertical" DockPanel.Dock="Top">
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="0">
<DockPanel>
<TextBlock Text="*.cfg File" Grid.Column="0" DockPanel.Dock="Left"/>
<Button Content="Browse..." Grid.Column="2" DockPanel.Dock="Right"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.cfg file..." Grid.Column="1" DockPanel.Dock="Right"/>
</DockPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="1">
<TextBlock Text="*.ps File " Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.ps file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="2">
<TextBlock Text="*.pic File " Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.pic file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="3">
<TextBlock Text="*.xlsx File" Grid.Column="0"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.xlsx file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="4">
<TextBlock Text="*.xlsx File"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.xlsx file..." Grid.Column="1"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
</StackPanel>-->
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="0">
<TextBlock MinHeight="20.5" Text="*.cfg File" Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.ps File " Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.pic File " Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.xlsx File" Grid.Column="0"/>
<TextBlock MinHeight="20.5" Text="*.xlsx File" Grid.Column="0"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="1">
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.cfg file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.ps file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse *.pic file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse Model mapping file..." Grid.Column="1" MinHeight="20.5"/>
<TextBox FontStyle="Italic" FontWeight="Light" Text="Browse Parameter mapping file..." Grid.Column="1" MinHeight="20.5"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Grid.Row="2">
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
<Button Content="Browse..." Grid.Column="2"/>
</StackPanel>
</StackPanel>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0">
<igDP:XamDataGrid x:Name="ItemsSource" DataContext="{Binding Source={StaticResource dataContext}, Path=ItemsSource, Mode=TwoWay}" Grid.Row="0" Margin="10" AutoFit="true">
<igDP:XamDataGrid.ViewSettings>
<igDP:GridViewSettings/>
</igDP:XamDataGrid.ViewSettings>
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings LabelTextAlignment="Left" AllowRecordFiltering="true" FilterOperandUIType="ExcelStyle" FilterStringComparisonType="CaseInsensitive" FilterOperatorDefaultValue="Contains"
LabelClickAction="SortByOneFieldOnlyTriState" SortComparisonType="Default"/>
</igDP:XamDataGrid.FieldSettings>
<igDP:XamDataGrid.FieldLayoutSettings>
<igDP:FieldLayoutSettings DataErrorDisplayMode="ErrorIconAndHighlight" SupportDataErrorInfo="RecordsAndCells" SelectionTypeRecord ="Single"
AutoGenerateFields="False" FilterUIType="FilterRecord"/>
</igDP:XamDataGrid.FieldLayoutSettings>
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
<igDP:FieldLayout.Fields>
<igDP:Field Name="IsSelected" Label="Select" HorizontalContentAlignment="Left" Width="Auto" VerticalContentAlignment="Center">
<igDP:Field.Settings>
<igDP:FieldSettings DataItemUpdateTrigger="OnCellValueChange">
<igDP:FieldSettings.LabelPresenterStyle>
<Style TargetType="{x:Type igDP:LabelPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" Content="" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.LabelPresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:Field>
<igDP:Field Label="Name" Name="Name" AllowEdit="False" HorizontalContentAlignment="Left" Width="Auto">
</igDP:Field>
<igDP:Field Label="Type" Name="Type" AllowEdit="False" HorizontalContentAlignment="Left" Width="*"/>
<igDP:Field Label="Background" Name="Background" AllowEdit="False" HorizontalContentAlignment="Left" Width="Auto"/>
<igDP:Field Label="Width" Name="Width" AllowEdit="False" HorizontalContentAlignment="Right" Width="Auto"/>
<igDP:Field Label="Height" Name="Height" AllowEdit="False" HorizontalContentAlignment="Right" Width="Auto"/>
</igDP:FieldLayout.Fields>
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>
</DockPanel>
<DockPanel Grid.Column="0" Grid.Row="1" Grid.RowSpan="2">
<StackPanel Orientation="Vertical" DockPanel.Dock="Bottom">
<TextBox Text="Sample Text1"/>
<TextBox Text="Sample Text2"/>
<TextBox Text="Sample Text3"/>
<TextBox Text="Sample Text4"/>
</StackPanel>
</DockPanel>
<!--</StackPanel>-->
</Grid>
</Window>
Xaml page code behind:
namespace UserInterface
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//GraphicViewModel obj = new GraphicViewModel();
//ItemsSource.DataSource = obj.ItemsSource;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Members
public GraphicViewModel GraphicViewModel
{
get { return this.DataContext as GraphicViewModel; }
set
{
this.DataContext = value;
if (this.DataContext != null)
NotifyPropertyChanged("GraphicViewModel");
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
}
}
}
Model Class:
namespace UserInterface.Model
{
public class GraphicsModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyOfPropertyChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
_isSelected = value;
NotifyOfPropertyChange("IsSelected");
}
}
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
if (_name != value)
_name = value;
NotifyOfPropertyChange("Name");
}
}
private string _type = string.Empty;
public string Type
{
get { return _type; }
set
{
if (_type != value)
_type = value;
NotifyOfPropertyChange("Type");
}
}
private string _width = string.Empty;
public string Width
{
get { return _width; }
set
{
if (_width != value)
_width = value;
NotifyOfPropertyChange("Width");
}
}
private string _height = string.Empty;
public string Height
{
get { return _height; }
set
{
if (_height != value)
_height = value;
NotifyOfPropertyChange("Height");
}
}
}
}
ViewModel class:
namespace UserInterface.ViewModel
{
public class GraphicViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyOfPropertyChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private ObservableCollection<GraphicsModel> _itemsSource = new ObservableCollection<GraphicsModel>();
// MainWindow _view = null;
public ObservableCollection<GraphicsModel> ItemsSource
{
get { return _itemsSource; }
set
{
if (_itemsSource == value) return;
_itemsSource = value;
NotifyOfPropertyChange("ItemsSource");
}
}
public GraphicViewModel()
{
//_view = view;
_itemsSource = new ObservableCollection<GraphicsModel>() { new GraphicsModel() { Name = "sdasdad", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,
new GraphicsModel() { Name = "sdsa", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "sdasdad", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "asas", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,new GraphicsModel() { Name = "rewwe", Type = "Model", IsSelected = false, Height = "1000", Width = "1370" } ,};
//view.GraphicViewModel = this;
}
}
}
When you write
<Window.Resources>
<dc:GraphicViewModel x:Key="dataContext"/>
</Window.Resources>
You are saying "create a property of type GraphicViewModel and name it dataContext". It's almost the exact same as doing something like this in the code-behind :
private GraphicViewModel dataContext = new GraphicViewModel();
When you write
<igDP:XamDataGrid DataContext="{Binding Source={StaticResource dataContext}, Path=ItemsSource, Mode=TwoWay}" .. />
You are saying "bind the igDP:XamDataGrid.DataContext property to dataContext.ItemsSource".
Now for why the Grid won't load, the .DataContext property doesn't actually do anything. It's just a holder for the data that sits behind the control. So if you were to write
<igDP:XamDataGrid ItemsSource="{Binding MyProperty}" />
It would be to tell WPF "set XamDataGrid.ItemsSource property equal to XamDataGrid.DataContext.MyProperty".
Since in the code above you set the DataContext equal to dataContext.ItemsSource, it would try to set the value to dataContext.ItemsSource.MyProperty (which of course does not exist).
What you actually need is something like
<igDP:XamDataGrid ItemsSource="{Binding Source={StaticResource dataContext}, Path=ItemsSource}" .. />
This binds the .ItemsSource property of the grid (which is the property it builds its data rows from) to the static resource created in <Window.Resources> named "dataContext", and the property on that object called ItemsSource.
Second issue here though is you seem to have multiple copies of your ViewModel in your code. Personally I recommend never creating objects in the XAML like you did for your GraphicViewModel. Instead, I would recommend something like this :
public MainWindow()
{
InitializeComponent();
this.DataContext = new GraphicViewModel();
}
Now it should be noted that this creates a new instance of the GraphicViewModel, but doesn't store it anywhere. If you want to access this object in other code-behind without casting this.DataContext to GraphicViewModel, then you should probably store the value somewhere.
Once the Window.DataContext is set, you can write the bindings without specifying a custom Source property for the binding.
<igDP:XamDataGrid ItemsSource="{Binding ItemsSource}" .. />
This will work because WPF controls will look up the DataContext from their parent by default if it is not specifically set. So in this case, the .DataContext property for XamDataGrid is null so it will travel up the visual tree looking for something with a DataContext until it runs into Window.DataContext which you've set in the constructor to a new instance of your GraphicViewModel.
For people looking to understand the DataContext property, I usually send them to this SO answer to avoid having to retype the same thing all the time. I'd recommend reading it if you're still working to understand what the DataContext is or how it's used.
Also that said, Dependency Injection is something different and wouldn't be used here at all.
Thanks a lot #Rachel, #Daniel Filipov & #quetzalcoatl, I got my code working now. The changes done in below files:
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
ItemsSource.DataContext= new GraphicViewModel();
}
MainWindow.xaml:
<igDP:XamDataGrid x:Name="ItemsSource" DataSource="{Binding ItemsSource, Mode=TwoWay}" .../>

WPF Databinding not working in both directions when DataGrid bound to Observable Collection of Properties with Sub properties

Excuse the wordy title, I'm having trouble with a succinct description. If I could come up with one, I could probably Google the right answer!
I am binding my DataGrid to an ObservableCollection of properties that themselves have properties. My grid is populated just fine, but when I edit the grid the changes are not getting back to my model.
I have an ObservableCollection
Normally, you'd just have some properties of MarriedCoupleRow, but I actually have something slightly more complicated. Each MarriedCoupleRow has some propties (Male, Female) which in turn expose properties (Height, Weight, Information). It's this Information that can be edited. Again, I can populate the grid just fine, but the setter property of Information is not hit when you edit the cell and tab off (or leave).
I'd appreciate any pointers or references, including how to better word my title!
Here's the simple code:
public class XMLDemoViewModel : ViewModelBase
{
public XMLDemoViewModel()
{
_rows = new ObservableCollection<MarriedCoupleRow>();
// create some data....
for (uint i = 0; i < 2;i++)
{
MarriedCoupleRow row = new MarriedCoupleRow();
row.Male = new HumanData();
row.Male.Height = (70 + i*5).ToString();
row.Male.Weight = 150+(i*30+1);
row.Male.Information = row.Male.Height + " " + row.Male.Weight;
row.Female = new HumanData();
row.Female.Height = (60 +i*3).ToString();
row.Female.Weight = 120+(i*10+5);
row.Female.Information = row.Female.Height + " " + row.Female.Weight;
_rows.Add(row);
}
}
#region Fields
private ObservableCollection<MarriedCoupleRow> _rows = null;
#endregion Fields
#region Properties
public ObservableCollection<MarriedCoupleRow> Rows
{
get
{
return _rows;
}
}
#endregion Properties
#region Commands
#endregion Commands
#region Private Methods
#endregion Private Methods
}
public class MarriedCoupleRow : ViewModelBase
{
private HumanData _Male = null;
public HumanData Male
{
get { return _Male; }
set
{
if (value != _Male)
{
_Male = value;
OnPropertyChanged("Male");
}
}
}
public HumanData Female { get; set; }
}
public class HumanData : INotifyPropertyChanged
{
public string Height { get; set; }
public uint Weight { get; set; }
private string _friendlyName;
public string Information
{
get
{
return _friendlyName;
}
set
{
if (_friendlyName != value)
{
_friendlyName = value;
OnPropertyChanged("Information");
}
}
}
}
And here's the XAML:
<Window x:Class="XMLDemo.Views.XMLDemoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="XMLDemoView" Height="600" Width="1152">
<DockPanel>
<StackPanel Orientation="Horizontal" Height="120" DockPanel.Dock="Bottom">
<StackPanel Orientation="Horizontal">
<GroupBox Width="249" BorderThickness="2" Height="90">
<GroupBox.Header>
<TextBlock FontSize="12" FontWeight="Bold">Control</TextBlock>
</GroupBox.Header>
<Grid Height="64" Width="223">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
</Grid>
</GroupBox>
</StackPanel>
</StackPanel>
<DataGrid ItemsSource="{Binding Path=Rows, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Male" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Male.Height}" IsEnabled="False" Grid.Row="0"></TextBox>
<TextBox Text="{Binding Male.Weight}" IsEnabled="False" Grid.Row="1"></TextBox>
<TextBox Text="{Binding Male.Information, Mode=TwoWay}" Grid.Row="2"></TextBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Female" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Female.Height}" IsEnabled="False" Grid.Row="0"></TextBox>
<TextBox Text="{Binding Female.Weight}" IsEnabled="False" Grid.Row="1"></TextBox>
<TextBox Text="{Binding Female.Information}" Grid.Row="2"></TextBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
Your TextBox.Text bindings need to be set to updatesource on propertychanged.
<TextBox Text="{Binding Male.Information, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Row="2"></TextBox>
There must be something goofy going on with LostFocus which is the default. I tested this using your code.
I Like your post. As I seen from the coding you are using Complex property binding with DataGrid. Also complex property changes won't be reflect in DataGrid.
However you can achieve this requirement in sample level. I will try to make the sample and let you know.
Regards,
Riyaj Ahamed I

Silverlight - What does my datacontext/binding path need to be here?

I've been working a bit with an image editor in Silverlight for the past week or so. It's my first encounter with it and I still haven't fully gotten my head around data bindings and datacontext or mvvm. I have a rotation method and I want to be able to pass the angle value from a text box on my MainPage.xaml to the method.I have an initial value set of 90 and my function rotates the image 90 degrees when I click it. The textbox is empty at runtime and also is clearly not updating my rotation angle.
MainPage.xaml
<Grid DataContext="{Binding Path=Project}" Height="70" Name="grid1" Width="200">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="84*" />
<ColumnDefinition Width="57*" />
<ColumnDefinition Width="59*" />
</Grid.ColumnDefinitions>
<Button Command="{Binding Path=RotateCWElementCommand}"
Height="30" Name="btnRotCW" Width="30" Grid.Column="2" Margin="15,0,14,5" Grid.Row="1">
<Image Source="../Assets/Images/Icons/object-rotate-right.png" Grid.Column="1"
Grid.Row="4" Height="20" HorizontalAlignment="Left" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="20" />
</Button>
<TextBlock FontWeight="Bold" HorizontalAlignment="Left" Margin="10,10,0,8" Text="Rotate" VerticalAlignment="Center" />
<TextBlock FontWeight="Bold" HorizontalAlignment="Left" Margin="34,9,0,10" Text="Angle:" VerticalAlignment="Center" Grid.Row="1" />
<TextBox Text="{Binding Path=RotateElement.Angle, Mode=TwoWay}" Height="24" Name="textBox1" Width="52" Grid.Column="1" Margin="0,6,5,5" Grid.Row="1" />
</Grid>
(Relevant code from)
Project.cs-
namespace ImageEditor.Client.BLL
{
public class Project : INotifyPropertyChanged
{
#region Properties
private RotateTransform rotateElement;
public RotateTransform RotateElement
{
get { return rotateElement; }
set
{
rotateElement = value;
NotifyPropertyChanged("RotateElement");
}
}
#endregion
#region Methods
private void RotateCWElement(object param)
{
FrameworkElement element = this.SelectedElement;
RotateTransform RotateElement = new RotateTransform();
RotateElement.Angle = 90;
RotateElement.CenterX = element.ActualWidth * 0.5;
RotateElement.CenterY = element.ActualHeight * 0.5;
element.RenderTransform = RotateElement;
}
What am I doing wrong here? Is it my datacontext or my binding path that is the problem? What should they be? Formatting is a little off sorry
the UI does not know that a property in your object has changed, since you only notify when your object changes, but not the properties in it:
private void RotateCWElement(object param)
{
FrameworkElement element = this.SelectedElement;
if (this.RotateElement == null) this.RotateElement = new RotateTransform();
RotateElement.Angle = 90;
RotateElement.CenterX = element.ActualWidth * 0.5;
RotateElement.CenterY = element.ActualHeight * 0.5;
element.RenderTransform = RotateElement;
//tell the UI that this property has changed
NotifyPropertyChanged("RotateElement");
}

Silverlight 4: Pattern for displaying data with a bit of logic?

I'm building a wp7 app.
I have a UserControl that displays a news article headline, teaser, and image. The entire class is pretty short:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story) : this()
{
this.Story = story;
Teaser.Text = story.Teaser;
Headline.Text = story.Title;
if (story.ImageSrc == null)
{
Thumbnail.Visibility = Visibility.Collapsed;
} else
{
Thumbnail.Source = new BitmapImage(story.ImageSrc);
}
}
}
And the corresponding XAML:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="Thumbnail" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock x:Name="Headline" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock x:Name="Teaser" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Is there a way to do with with less code-behind and more XAML? Some way to use binding to bind the text for Headline and Teaser to properties of the Story, while not crashing if Story is null?
About about the image? I've got a bit of logic there; is there some way to automatically do that in XAML, or am I stuck with that in C#?
Looks like a ViewModel is in order:
public class StoryViewModel
{
readonly Story story;
public StoryViewModel(Story story)
{
this.story = story;
}
public string Teaser { get { return story == null ? "" : story.Teaser; } }
public string Title { get { return story == null ? "" : story.Title; } }
public bool IsThumbnailVisible { get { return story != null && story.ImageSrc != null; } }
public BitmapImage Thumbnail { get { return IsThumbnailVisible ? new BitmapImage(story.ImageSrc) : null; } }
}
Making your codebehind nice and simple:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story)
: this()
{
this.DataContext = new StoryViewModel(story);
}
}
And your XAML becomes a set of bindings:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisiblityConverter"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Visibility="{Binding IsThumbnailVisible, Converter={StaticResource booleanToVisiblityConverter}}" Source="{Binding Thumbnail}" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock Text="{Binding Title}" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock Text="{Binding Teaser}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Ok, it would probably be possible to do this with just model (story) and view (xaml) via fallbackvalues and more complex converters, but i hope you'll find that viewmodels give you the most power in terms of testable, brick-wall-less, view-specific logic...

Resources