How does WPF know to use INotifyPropertyChanged when I bind to IEnumerable? - c#

In a view model(SomeViewModel below), a Data property returns IEnumerable<IData> where both interfaces do not implement INotifyPropertyChanged.
However, the underlying Data field is ObservableCollection<ObservableData> and both classes implement the INotifyPropertyChanged.
Finally in XAML, `Data is bound to a DataGrid.
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="True"/>
I thought this binding could introduce the binding memory leak described in KB938416, but to my surprise it does not.
When the method ChangeData is called, I can see DataGrid is updated and the OnPropertyChanged called a handler.
My question is: How does WPF know to use INotifyPropertyChanged when the bound data returns IEnumerable<IData> (that both do not implement INotifyPropertyChanged)??
public interface IData
{
string Name { get; }
}
// In addition to IData, implements INotifyPropertyChanged
public class ObservableData : IData, INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return this._name; }
set
{
if (_name == value) { return; }
_name = value;
OnPropertyChanged("Name");
}
}
// 'OnPropertyChanged' omitted for brevity
}
// here is some ViewModel
public class SomeViewModel
{
private ObservableCollection<ObservableData> _data = new ObservableCollection<ObservableData>();
// In XAML, a DataGrid's ItemsSource is bound to this.
public IEnumerable<IData> Data { get { return _data; } }
public void ChangeData()
{
// test OC's notification
_data.Add(new ObservableData {Name = "new" });
// test ObservableData's notification
_data[0].Name += " and changed";
}
}

In your case INotifyPropertyChanged is not needed for the Data property.
Data is of type ObservableCollection which internally implements INotifyCollectionChanged.
Due to this the view gets notified whenever you add or remove items.

Even though your Data property is returned with the type of IEnumerable<IData>, the object itself is still a ObservableCollection<ObservableData>. WPF can just use the is or as operators to test whether any particular object implements INotifyPropertyChanged, regardless of the handle provided.
IEnumerable<IData> test = Data;
if (test is INotifyPropertyChanged) {
//This if block enters because test is really ObservableCollection<ObservableData>
INotifyPropertyChanged test2 = (INotifyPropertyChanged)test;
}

Related

Trigger PropertyChanged of the certain element in collection

Having two collections in the ViewModel, one is working as a source of rows for the DataGrid (in this case have only values 1,2,3,..), and other object that represents sequencer for one column in the DataGrid (based on ID expose some value in property).
In the example below I have used default ObservableObject and FullyObservableCollection behaviour, but it was also tried with other kinds of collections with no success. WPF behavior was tested on both DataGridComboBoxColumn and DataGridTemplateColumn with ComboBox.
ViewModel:
public class ViewModel : ObservableObject
{
private FullyObservableCollection<Seq> mSequencer;
public FullyObservableCollection<Seq> Sequencer
{
get { return mSequencer; }
set { SetProperty(ref mSequencer, value); }
}
private FullyObservableCollection<RowSrc> mRowSource;
public FullyObservableCollection<RowSrc> RowSource
{
get { return mRowSource; }
set { SetProperty(ref mRowSource, value); }
}
}
Class definitions:
public class Seq : ObservableObject
{
private int mId;
public int Id
{
get { return mId; }
set { SetProperty(ref mId, value); }
}
private string mName;
public string Name
{
get { return mName; }
set { SetProperty(ref mName, value); }
}
}
public class RowSrc : ObservableObject
{
private int mValue;
public int Value
{
get { return this.mValue; }
set { SetProperty(ref mValue, value); }
}
}
View - XAML:
<CollectionViewSource x:Key="Proxy" Source="{Binding Sequencer}"/>
<DataGrid ItemsSource="{Binding RowSource}">
<DataGrid.Columns>
<DataGridComboBoxColumn
ItemsSource="{Binding Source={StaticResource Proxy}"
SelectedValueBinding="{Binding Value}"
SelectedValuePath="Id"
DisplayMemberPath="Name"/>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
From the above, datagrid is initiliazed with the values of RowSource, that will go throught binding tunnel to the Proxy, which will lead to Sequencer. Based on the Value of each row, object with same Id will be returned from the Sequencer collection.
As is, on the first start everything works well. Problem starts, when we want to empty the Sequencer and fill it again.
Sequencer.Clear();
Sequencer.AddRange(...);
Now the items in the Sequencer refresh, however the binding between Row[n].Combobox and the items in Sequencer DO NOT REFRESH. The reason behind this is that the properties themselves has not changed, only items in collection.
Workaround - hack:
public class RowSrc
{
...
public void InvokeChange()
{
OnPropertyChanged(nameof(Value));
}
}
public class ViewModel
{
Sequencer.Clear();
Sequencer = GenerateNewCollection();
//after update of Sequencer
foreach (var nRowSrc in RowSrc) nRowSrc.InvokeChange();
}
However this means that we would need to add this specific method to each of our Model classes, and then remember to call them on each of Sequencer change.
Q: How to invoke property change automatically, when the collection changed OR how to handle this binding correctly?
For the problem you posted, is very simply, just use an ObsevrableCollection instead of a List. In fact, ObsevrableCollection dose not only report Add and Remove, it report all kind of movement that support by INotifyCollectionChanged.CollectionChanged event. So if you use indexer of ObsevrableCollection to set an element, a CollectionChanged of type NotifyCollectionChangedAction.Replace will be raised, and the binding engine will handle it fine from that. But I am a little confused after read you comment, and can not be sure if this is really what you want.
You can raise the PropertyChanged event for your Property Prop1 through calling MyList[0].OnPropertyChanged(nameof(Foo.Prop1));.
There is no need to create a TriggerPropertyChanged method.
This works, because in your example OnPropertyChanged is public and optionally accepts a string, that describes the property name.

WPF: Binding is lost when bindings are updated

I have a WPF-Application for controlling a WCF-RESTful service, i.e. for starting, initializing and stopping it. Therefore I have a MainWindow UI which contains a UserControl to configure settings. When I initialize my service, some data is loaded into DependencyProperties and ObservableCollections to display it in the GUI. Here is the part of the method where I update these settings:
public partial class MainWindow : Window {
private void InitializeService (bool reInitialize = false) {
var restService = (RestService)this.ServiceHost.SingletonInstance;
var settings = restService.GetSettings();
//UCSettings is the "x:name" of the embedded UserControl "UserControlSettings" in this window
this.UCSettings.ExecutionTimes.Clear();
settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");
}
}
public partial class UserControlSettings : UserControl {
public ObservableCollection<ExecutionTime> ExecutionTimes { get; set; }
public static readonly DependencyProperty TableConfigurationLoaderProperty = DependencyProperty.Register("TableConfigurationLoader", typeof(Setting), typeof(UserControlSettings), new FrameworkPropertyMetadata(default(Setting)));
public Setting TableConfigurationLoader {
get { return (Setting)this.GetValue(TableConfigurationLoaderProperty); }
set { this.SetValue(TableConfigurationLoaderProperty, value); }
}
}
public class Setting {
public string Name { get; set; }
public bool IsEnabled { get; set; }
public int ExecutionTimeId { get; set; }
}
public class ExecutionTime {
public int Id { get; set; }
public string Value { get; set; }
}
In the Code-Designer (UserControlSettings.xaml.cs) these properties are used in some bindings for a ComboBox:
<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
<ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>
</UserControl>
When I first load in the data with the InitializeService method, everything works fine. The ComboBox is filled with the data of the ObservableCollection and the matching value is selected automatically by the ExecutionTimeId.
When I try to "reinitialize" the service, I call the same method again, but the SelectedValue binding does not work anymore. I checked the values of these properties in the debugger, but they are set correctly in this method again. What am I doing wrong here? Some samples:
Correct display first load:
Incorrect display seconds load:
TableConfigurationLoader is a dependency property. That means a lot of things, but one of them is that when you change the value of TableConfigurationLoader to a different instance of Setting, an event is raised, and this Binding handles that event and updates SelectedValue on the combo box:
SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}"
However, Setting.ExecutionTimeId isn't a dependency property. It's a regular .NET CLR property, which doesn't notify anybody of anything when its value changes. So if you change the ExecutionTimeId property of the same old Setting that's already in TableConfigurationLoader, nobody knows and nothing happens.
Since Setting is not a control class, you don't particularly need or want its properties to be dependency properties. Instead, you can treat it as a viewmodel. In implementation terms, all a viewmodel really is, is any class that implements INotifyPropertyChanged. With changes to Setting shown below, I think the binding should work as you expect, if I correctly understand your problem. I've changed IsEnabled so it will raise PropertyChanged as well; you may not actually need that, but it's illustrative.
You may need to do the same with your ExecutionTime class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public class Setting : ViewModelBase
{
public string Name { get; set; }
#region IsEnabled Property
private bool _isEnabled = false;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
#endregion IsEnabled Property
#region ExecutionTimeId
private int _executionTimeId = 0;
public int ExecutionTimeId
{
get { return _executionTimeId; }
set
{
if (value != _executionTimeId)
{
_executionTimeId = value;
OnPropertyChanged();
}
}
}
#endregion ExecutionTimeId
}
There are three (ish) mechanisms in WPF for notifying things that properties have changed, and you need to be using one or another somehow if you want things to update correctly:
Dependency properties of dependency objects: For properties of controls
INotifyPropertyChanged: For properties of viewmodels
INotifyCollectionChanged: For collections.
A collection property should also raise INotifyPropertyChanged.PropertyChanged when you assign a new collection instance to it. A given instance of the collection will handle raising its own events when its contents change.
ObservableCollection<T> and ReadOnlyObservableCollection<T> implement INotifyCollectionChanged so you don't have to; it's a big hassle to implement that one properly so you really don't want to go there.
Creating a new instance of Setting before referring to the actual object solved my problem. It seems that the reference to the specific property of Setting is lost, if I just "override" the existing instance of this property:
var settings = restService.GetSettings();
this.UCSettings.ExecutionTimes.Clear();
settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
this.UCSettings.TableConfigurationLoader = new Setting();
this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");
Try adding UpdateSourceTrigger=PropertyChanged to the binding like this
<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
<ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, UpdateSourceTrigger=PropertyChanged,Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>

How I can bind property, which use another static property

I use mvvm pattern in my progeсt (C#), and I have some problem.
I have a label on my view, and label's text is binded to property from my viewModel:
val label=new Label();
label.SetBinding<StatusViewModel>(Label.TextProperty, x=>x.TextProp);
this is my view model, which implements INotifyPropertyChanged interface:
class StatusViewModel
{
private string _textProp;
public string TextProp
{
get
{
return _textProp;
}
set
{
if(_textProp == value)
return _textProp;
_textProp=value;
OnPropertyChange();
}
}
}
but I have another static property:
static class StaticClass
{
public static string StaticText {get; set; }
}
And I want use this static property StaticText inside my TextProp property from StatusViewModel. And StaticText property mast notify label about it changes.
P.S. sorry about possible mistakes, I typed this code from my head.
If you are binding to static properties, you are probably doing it wrong :)
That said, the initial bind is super easy. You just need to add a property that returns the static one:
public string StaticTextRedirect
{
get { return StaticClass.StaticText; }
set { StaticClass.StaticText = value; }
}
The PropertyChanged event is another beast. You could raise it from the StaticTextRedirect property of course, but that won't fire if some other class changes the property. You'll probably need to just raise a custom event in the static property's setter that client code can listen to and raise the appropriate PropertyChanged event for.

MVVM and INotifyPropertyChanged Issue

I have a big problem with MVVM design. I am trying to catch every PropertyChanged of my inner nested objects, including futhermore propertchanged of their nested objects, inside my ViewModel but I dont know how to do it.
Here is my structure:
class MyVM
{
public MyVM()
{
this.SomeData = new SomeData();
this.SomeData.NestedObj = new MyNestedDat();
this.SomeData.Str = "This tiggers propertychanged inside MyDat class";
// this triggers propertychanged event inside MyNestedDat class
this.SomeData.NestedObj.Num = 123;
}
// and here should be a method where i catch all possibe propertychanges from my nested objets and their nested objets, how do i do that?
public MyDat SomeData
{
get;
set;
}
}
class MyDat : INotifyPropertyChanged
{
private string str;
public string Str;
{
get { return this.str;}
set
{
this.str = value;
this.PropertyChanged(this, "Str");
}
}
publicMyNestedDat NestedObj
{
get;
set;
}
}
class MyNestedDat : INotifyPropertyChanged
{
private int num;
public int Num
{
get{ return this.num;}
set
{
this.num = value;
this.PropertyChanged(this, "Num");
}
}
}
How do i get this to work? I am really clueless where to start.
MyNestedDat class throws PropertyChanged, MyDat class throws propertychanged and i want to catch them all inside my viewmodel. How can i do that?
In my opinion there are a few conceptual things wrong with what you are asking. Just imagine you get a solution that works for your scenario (that you are happy with) and consider the following:
What happens if another layer is added? do you still expect it to work the same?
Should property changes be propagated (viewModel1.propA notifies viewModel2.PropA)?
Should property changes be transformed (viewModel1.SomeProp notifies ViewModel2.AnotherProp)?
Is performance a concern? how will this perform if you need to propagate the property changed events through many levels?
This should be raising alarm bells that the current approach is not the right path to tread.
What you need is a way to provide communication between your viewModels in a loosely coupled way so that you viewModels do not even need to know about each others existence. The beauty of this is that this will also work in other situations not just for property changes.
For your case of property changed events, one viewModel wants to know when something happens (it could be something other than a property changed event, remember). This means the other viewModel needs some way of saying "Hey, a property has changed" (or "My state has changed", "That database call has finished" etc).
Now in C# you can provide events which provide this feature....except, now your objects know about each other which leaves you with the same problem you had before.
To overcome this problem you need another object, a mediator (lets call it Messenger in this example), whose sole purpose is to handle the message passing between the objects so that they can live in ignorance of each other.
The general idea is this. In the viewModel that provides notifications you might do something like this:
public string MyProp
{
get { return _myProp; }
set
{
_mProp = value;
OnPropertyChanged("MyProp");
Messenger.PostMessage(new VMChangedMessage { ViewModel = this, PropertyName = "MyProp" });
}
}
And in the viewModel that is interested in the event you might do something like this:
public class ViewModel2
{
public ViewModel2()
{
Messenger.Subscribe<VMChangedMessage>(handleMessage);
}
private void handleMessage(VMChangedMessage msg)
{
// Do something with the information here...
}
}
Notice that the two viewModels never reference each other. They are now loosely-coupled.
There are a number of pre-existing implementations already available and it isn't difficult to create your own (the messenger basically keeps a list of objects that are interested in a certain message and iterates the list when it needs to notify the interested parties). There are a few things that can be implemented differently (some implementations just pass string messages around rather than encapsulating the information in objects, and some handle the clean-up of observers automatically).
I would recommend using Josh Smiths (excellent) MVVM Foundation which includes a messenger class. It's also open source so you can see how it works.
There is no clear constraint about what PropertyName should contains in PropertyChangedEventArgs.
See Subscribe to INotifyPropertyChanged for nested (child) objects.
Here is an example :
class A : BaseObjectImplementingINotifyPropertyChanged {
private string m_name;
public string Name {
get { return m_name; }
set {
if(m_name != value) {
m_name = value;
RaisePropertyChanged("Name");
}
}
}
}
class B : BaseObjectImplementingINotifyPropertyChanged {
private A m_a;
public A A {
get { return m_a; }
set {
if(m_a != value) {
if(m_a != null) m_a.PropertyChanged -= OnAPropertyChanged;
m_a = value;
if(m_a != null) m_a.PropertyChanged += OnAPropertyChanged;
RaisePropertyChanged("A");
}
}
}
private void OnAPropertyChanged(object sender, PropertyChangedEventArgs e) {
RaisePropertyChanged("A." + e.PropertyName);
}
}
B b = new B();
b.PropertyChanged += (s, e) => { Console.WriteLine(e.PropertyName); };
b.A.Name = "Blah"; // Will print "A.Name"
The best thing to do here is to separate the idea of a Model and a ViewModel.
By having a ViewModel object that is flatter than the Model you can avoid this scenario. Using an automatic mapping tool like Automapper then allows you to map the Model to the ViewModel and vice versa.
https://github.com/AutoMapper/AutoMapper/wiki/Flattening
class MyDatViewModel : INotifyPropertyChanged
{
public string Str
{
// ... Get Set
}
public int NestedObjNum
{
// ... Get set
}
}
// Configure AutoMapper
Mapper.CreateMap<MyDat, MyDatViewModel>();
// Perform mapping
MyDatViewModel viewModel = Mapper.Map<MyDat, MyDatViewModel>(someData);

WPF DataGrid two-way source binding to List<DataClass> programmatically

I need do the binding at run time since the DataGrid will be bound to different data sources and/or class objects. I have two classes CA and CB. In CB, there is a method to set up binding. But the simple way below only sets one way binding. How to do two-way binding? I.E. when an element in List<CA> list changed, the DataGrid1 will automatically updated.
class CA
{
private int a = 1;
private string b = "";
private bool c = true;
public int A { get { return a; } set { a = value; } }
public string B { get { return b; } set { b = value; } }
public bool C { get { return c; } set { c = value; } }
}
class CB
{
List<CA> datalist = new List<CA>();
private void SetBinding(ref List<CA> ca, ref DataGrid dg)
{
dg.ItemsSource = ca;
}
}
In order to be bound correctly, your class CA should implement INotifyPropertyChanged interface. See: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
And all the properties should raise the propertyChanged event to notify databound controls.
Here you can find simple example: http://msdn.microsoft.com/en-us/library/ms229614.aspx
Also, I'd recommend using ObservableCollection instead of List with databinding, since it has INotifyPropertyChanged out of the box and notifies databound control of elements additions, deletions and changes.
Your class "CA" will need to implement INotifyPropertyChanged for changes to the CA objects to be reflected in your datagrid.
If you also want the datagrid to be aware of items being added and removed then you will need to use an ObservableCollection instead of List or at least a collection that implements INotifyCollectionChanged.

Categories

Resources