I have MyCollection of MyPOCO object (that has two string properties).
When I try to implement a HierarchicalDataTemplate into a treeview the binding is not working, I get the class name.
I know that if i take out the datatemplate from the control everything works fine but i am interested to see why this example is not working.
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource testVM}}">
<sdk:TreeView Margin="8,8,8,111" ItemsSource="{Binding MyCollection}">
<sdk:HierarchicalDataTemplate ItemsSource="{Binding MyPOCO}">
<sdk:HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Property1}"/>
<TextBlock Text="{Binding Property2}"/>
</StackPanel>
</DataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView>
</Grid>
Here is the ViewModel also.
namespace MyPOCProject
{
public class MyPOCO
{
private string property1;
public string Property1
{
get { return property1; }
set { property1 = value; }
}
private string property2;
public string Property2
{
get { return property2; }
set { property2 = value; }
}
public MyPOCO(string p1, string p2)
{
property1 = p1;
property2 = p2;
}
}
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyPOCO> myCollection;
public ObservableCollection<MyPOCO> MyCollection
{
get { return myCollection; }
set { myCollection = value; RaisePropertyChanged("MyCollection"); }
}
public MyViewModel()
{
MyPOCO _poco1 = new MyPOCO("aaa1", "bbb1");
MyPOCO _poco2 = new MyPOCO("aaa2", "bbb2");
MyPOCO _poco3 = new MyPOCO("aaa3", "bbb3");
MyCollection = new ObservableCollection<MyPOCO>() { _poco1, _poco2, _poco3 };
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
So what am I doing wrong?
AGAIN ... I am interested in this particular example. I want to know what's wrong with this example and why.
Thanks.
The code you posted is not hierarchical, In other words: The MyPOCO Class is not containing a property MyCollection<MYPOCO> Children.
Here is an example for the HierarchicalDataTemplate
Xaml:
<sdk:TreeView x:Name="MyTreeView"
HorizontalAlignment="Left"
ItemsSource="{Binding MyCollection}"
Width="200" Height="280">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
<sdk:HierarchicalDataTemplate.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
</sdk:HierarchicalDataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Codebehind classes:
public class MyPoco
{
public MyPoco(string header, int sampleChildrenCount)
{
this.Header = header;
this.Children = new ObservableCollection<MyPoco>();
if (sampleChildrenCount > 0)
{
for (int i = 0; i < sampleChildrenCount; i++)
{
string newHeader = String.Format("Test {0}", sampleChildrenCount * i);
var myPoco = new MyPoco(newHeader, sampleChildrenCount - 1)
this.Children.Add(myPoco);
}
}
}
public string Header { get; set; }
public ObservableCollection<MyPoco> Children { get; set; }
}
public class MyViewModel
{
public MyViewModel()
{
MyCollection = new ObservableCollection<MyPoco>();
for (int i = 0; i < 6; i++)
{
this.MyCollection.Add(new MyPoco(String.Format("Test {0}", i), 5));
}
}
public ObservableCollection<MyPoco> MyCollection { get; set; }
}
Codebehind startup:
public MainPage()
{
public MainPage()
{
InitializeComponent();
MyTreeView.DataContext = new MyViewModel();
}
}
Related
How can I make it so when I change a property of a collection member in a collection it updates in the datagrid display?
In my example I have an ObservableCollection of Employees. Each employee has a List property.
When I assign the Employee's Car value to a new List it will update successfully. When I assign the Employee's Car's Model property it doesn't update.
MainWindow.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="DataBindings.MainWindow" Title="MainWindow" Height="332" Width="474" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<CollectionViewSource x:Key="GridDataSource" Source="{Binding Employees}" />
</Window.Resources>
<Grid Margin="0,0,0,-1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="1" ItemsSource="{Binding Source={StaticResource GridDataSource}}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,0,0,114">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Names" Width="Auto" Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn x:Name="Cars" Width="Auto" Header="Cars" Binding="{Binding CarsString}" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Update" Click="Update" HorizontalAlignment="Left" Margin="170,202,0,-20" Grid.Row="1" VerticalAlignment="Top" Width="75" />
</Grid>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace DataBindings
{
public partial class MainWindow : Window
{
DAL dataAccess = new DAL();
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<Employee> Employees { get { return dataAccess.EmployeesList; } }
private void Update(object sender, RoutedEventArgs e)
{
Employees[1].Name = "Mike"; //changing the name property on the collection of employees works
Debug.WriteLine($"Mike's Car is a {Employees[1].Cars[0].Model}");
Employees[1].Cars[0].Model = "Volvo"; //changing the model of car in a cars list does not
Debug.WriteLine($"Mike's Car is a {Employees[1].Cars[0].Model}");
}
}
}
DAL.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace DataBindings
{
public class DAL : ObservableCollection<Employee>
{
public ObservableCollection<Employee> EmployeesList = new ObservableCollection<Employee>();
public DAL()
{
List<Car> carList = new List<Car>();
carList.Add(new Car { Model = "Ford" });
carList.Add(new Car { Model = "Honda" });
EmployeesList.Add(new Employee { Name = "Bob", Cars = carList });
EmployeesList.Add(new Employee { Name = "John", Cars = carList });
//EmployeesList.CollectionChanged += EmployeesList_CollectionChanged;
}
}
public class Employee : INotifyPropertyChanged
{
private string empName;
public List<Car> empCars = new List<Car>();
private string carsString;
public string Name
{
get { return this.empName; }
set
{
if (this.empName != value)
{
empName = value;
this.NotifyPropertyChanged("Name");
};
}
}
public List<Car> Cars
{
get { return this.empCars; }
set
{
if (this.empCars != value)
{
empCars = value;
carsToString(empCars);
this.NotifyPropertyChanged("Cars");
};
}
}
public string CarsString
{
get { return this.carsString; }
set
{
if (this.carsString != value)
{
carsString = value;
this.NotifyPropertyChanged("CarsString");
};
}
}
public void carsToString(List<Car> Cars)
{
string carString = "";
foreach (Car car in Cars)
{
carString += car.Model + " ";
}
CarsString = carString;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Car : INotifyPropertyChanged
{
private string carModel;
public string Model
{
get { return this.carModel; }
set
{
if (this.carModel != value)
{
carModel = value;
this.NotifyPropertyChanged("Model");
};
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I modified your code a bit, which IMO can be improved more.
Here is my xaml:
<Window.DataContext>
<local:DAL />
</Window.DataContext>
<Grid Margin="0,0,0,-1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid x:Name="grid" Grid.Row="1" ItemsSource="{Binding EmployeesList}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,0,0,114">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Cars">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CarsString, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Update" Click="Update" HorizontalAlignment="Left" Margin="170,202,0,-20" Grid.Row="1" VerticalAlignment="Top" Width="75" />
</Grid>
Here's my code-behind:
You can have an ICommand to your UpdateButton so you can leave your code behind clean: https://www.c-sharpcorner.com/UploadFile/e06010/wpf-icommand-in-mvvm/
public DAL VM => (DAL) DataContext;
public MainWindow()
{
InitializeComponent();
}
private void Update(object sender, RoutedEventArgs e)
{
VM.EmployeesList[1].Name = "Mike"; //changing the name property on the collection of employees works
Debug.WriteLine($"Mike's Car is a {VM.EmployeesList[1].Cars[0].Model}");
VM.EmployeesList[1].Cars[0].Model = "Volvo"; //changing the model of car in a cars list does not
Debug.WriteLine($"Mike's Car is a {VM.EmployeesList[1].Cars[0].Model}");
}
Here's my DAL.cs
Notice that I changed your List to ObservableCollection then in accessors, you need to make sure that CarsString is updated, since you're not directly updating it in your view.
public class DAL : INotifyPropertyChanged
{
private ObservableCollection<Employee> _employeesList;
public ObservableCollection<Employee> EmployeesList
{
get => _employeesList;
set
{
_employeesList = value;
OnPropertyChanged();
}
}
public DAL()
{
EmployeesList = new ObservableCollection<Employee>();
ObservableCollection<Car> carList = new ObservableCollection<Car>();
carList.Add(new Car { Model = "Ford" });
carList.Add(new Car { Model = "Honda" });
EmployeesList.Add(new Employee { Name = "Bob", Cars = carList });
EmployeesList.Add(new Employee { Name = "John", Cars = carList });
//EmployeesList.CollectionChanged += EmployeesList_CollectionChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Employee : INotifyPropertyChanged
{
private string empName;
public ObservableCollection<Car> empCars = new ObservableCollection<Car>();
private string carsString;
public string Name
{
get { return this.empName; }
set
{
if (this.empName != value)
{
empName = value;
this.NotifyPropertyChanged("Name");
};
}
}
public ObservableCollection<Car> Cars
{
get
{
carsToString(empCars);
this.NotifyPropertyChanged("CarsString");
return this.empCars;
}
set
{
if (this.empCars != value)
{
empCars = value;
carsToString(empCars);
this.NotifyPropertyChanged("Cars");
this.NotifyPropertyChanged("CarsString");
};
}
}
public string CarsString
{
get { return this.carsString; }
set
{
if (this.carsString != value)
{
carsString = value;
this.NotifyPropertyChanged("CarsString");
};
}
}
public void carsToString(ObservableCollection<Car> Cars)
{
string carString = "";
foreach (Car car in Cars)
{
carString += car.Model + " ";
}
CarsString = carString;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Car : INotifyPropertyChanged
{
private string carModel;
public string Model
{
get { return this.carModel; }
set
{
if (this.carModel != value)
{
carModel = value;
this.NotifyPropertyChanged("Model");
};
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
I am trying to bind DataGrid using MVVM approach in WPF, model is getting values but nothing is showing in DataGrid
Following is my code
public class TalleyEditorGrid : INotifyPropertyChanged
{
#region Properties
private string _Quantity;
private string _Ft;
private string _Inch;
private string _Comment;
public string Quantity { get { return _Quantity; } set { _Quantity = value; NotifyPropertyChanged("Quantity"); } }
public string Ft { get { return _Ft; } set { _Ft = value; NotifyPropertyChanged("Ft"); } }
public string Inch { get { return _Inch; } set { _Inch = value; NotifyPropertyChanged("Inch"); } }
public string Comment { get { return _Comment; } set { _Comment = value; NotifyPropertyChanged("Comment"); } }
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); }
}
In my xaml.cs
private ObservableCollection<TalleyEditorGrid> _TalleyEditorGrid = new ObservableCollection<TalleyEditorGrid>();
public ObservableCollection<TalleyEditorGrid> TalleyEditorCol
{
get { return _TalleyEditorGrid; }
}
On button click I am filling this collection
_TalleyEditorGrid.Add(new TalleyEditorGrid() { Quantity = Q, Ft = FT, Inch = In, Comment = Comment});
Xaml as follows
<DataGrid x:Name="TalleyEditor" ItemsSource="{Binding Path=TalleyEditorCol}" AutoGenerateColumns="True" Visibility="Collapsed"
CanUserAddRows="False"
HorizontalGridLinesBrush="{x:Null}"
VerticalGridLinesBrush="Silver"
Background="White"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
SelectionMode="Single"
SelectionUnit="FullRow"
CanUserReorderColumns="True"
CanUserSortColumns="True" DataGridCell.GotFocus="TalleyEditor_GotFocus"
RowHeaderWidth="0" HorizontalAlignment="Left" VerticalAlignment="Top" RowHeight="17" ColumnHeaderHeight="21" PreviewKeyDown="TalleyEditor_PreviewKeyDown" LostFocus="TalleyEditor_LostFocus" CellEditEnding="myDG_CellEditEnding">
Set the DataContext of the window to itself:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
Or set the DataContext of the DataGrid:
public MainWindow()
{
InitializeComponent();
TalleyEditor.DataContext = this;
}
If you put the DataGrid inside another DataGrid, you need to use a {RelativeSource} to be able to bind to a property of the parent window:
<DataGrid ... ItemsSource="{Binding Path=TalleyEditorCol, RelativeSource={RelativeSource AncestorType=Window}}">
</DataGrid>
I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.
I have below class hierarchy.
public class RowViewModel : BaseViewModel
{
private CellViewModel cell;
public CellViewModel Cell
{
get { return cell; }
set
{
cell = value;
OnPropertyChanged("Cell");
}
}
}
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{ return cellLinks; }
set
{
cellLinks = value;
OnPropertyChanged("CellLinks");
}
}
}
public class CellLinkViewModel : BaseViewModel
{
public string Text
{
get { return CellValue.Text; }
set
{
CellValue.Text = value;
OnPropertyChanged("Text");
}
}
public CellValueViewModel CellValue { get; set; }
}
public class CellValueViewModel : BaseViewModel
{
public string Text { get; set; }
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
XAML code looks like,
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Cell.Text}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Cell.CellLinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
CellValueViewModel is shared across multiple CellLinkViewModel. When I change CellValueViewModel it should have propagated changes to all dependent parents and DataGrid should show all latest values.
This is result
But I think its not happening automatically, am I missing something here? How to notify all datagrid cells to update automatically when the nested objects gets updated.
You need to hook up event handlers to the PropertyChanged event for all CellLinkViewModel objects and raise the PropertyChanged event for the Text property of the CellViewModel whenever a link is modified, e.g.:
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{
return cellLinks;
}
set
{
cellLinks = value;
if(cellLinks != null)
{
foreach(var link in cellLinks)
{
link.PropertyChanged += Link_PropertyChanged;
}
}
OnPropertyChanged("CellLinks");
}
}
private void Link_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("Text");
}
}
You probably also want to handle the CollectionChanged event for the ObservableCollection<CellLinkViewModel> and hook up event handlers to dynamically added CellLinkViewModel objects as well.
I am trying to successfully TwoWay bind an ObservableCollection to TextBoxes in a DataTemplate. I can get the data to display properly, but I am unable to change the list data through the UI. I have a Model class named 'model' which contains an ObservableCollection named 'List'. The class implements the INotifyPropertyChanged interface. Here is the xaml for the shell. The DataContext for Window1's grid is set to "theGrid.DataContext=model"
<Window x:Class="BindThat.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindThat"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
This is the code for the Model class:
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
Could anyone advise if I am going about this the correct way?
You need a property to bind two way, so string is not good for this.
Wrap it in a string object, like this:
public class Model
{
public ObservableCollection<StringObject> List { get; private set; }
public Model()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"},
};
}
}
public class StringObject
{
public string Value { get; set; }
}
and bind to Value property instead of "."
Also, you don't need to notify of a change in observable collection, so until your model has some other propertis of its own, it does not need to have INotifyPropertyChange. If you want your ItemsControl react to changes in the individual StringObjects, then you should add INotifyPropertyChanged to a StringObject.
And yet again, two way binding is default, so you need only
<TextBox Text="{Binding Path=Value}" />
in your binding.
I believe you need to derive your collection items from DependencyObject for TwoWay binding to work. Something like:
public class DependencyString: DependencyObject {
public string Value {
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(DependencyString), new UIPropertyMetadata(""));
public override string ToString() {
return Value;
}
public DependencyString(string s) {
this.Value = s;
}
}
public class Model {
private ObservableCollection<DependencyString> _list = new ObservableCollection<DependencyString>();
public ObservableCollection<DependencyString> List {
get { return _list; }
}
public Model() {
List.Add(new DependencyString("why"));
List.Add(new DependencyString("not"));
List.Add(new DependencyString("these?"));
}
}
...
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
xaml view:
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
in code behind in the constructor:
DataContext = new ViewModel();
in ViewModel Class:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<StringObject> _List = new ObservableCollection<StringObject>();
public ObservableCollection<StringObject> List
{
get { return _List; }
set
{
_List = value;
NotifyPropertyChanged("List");
}
}
public ViewModel()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"}
};
}
}
public class StringObject
{
public string Value { get; set; }
}
Be careful with a collection with type string it doesn't work, you have to use an object => StringObject