I am trying to figure out how to update my bool properties inside a ViewModel using
INotifyPropertyChanged?
Basically in my ViewModel I pass in a List of string. Each boolean properties check the list to see if a
string value exists.
Now in my software lifecycle the list will get updated and inturn I would like to update each properties
using INotifyPropertyChanged.
My question is how do I invoke the INotifyPropertyChanged from a AddToList method? Is using a method for this the
correct direction?
public class ViewModel : INotifyPropertyChanged
{
private List<string> _listOfStrings;
public ViewModel(List<string> ListOfStrings)
{
_listOfStrings = ListOfStrings;
}
public bool EnableProperty1 => _listOfStrings.Any(x => x == "Test1");
public bool EnableProperty2 => _listOfStrings.Any(x => x == "Test2");
public bool EnableProperty3 => _listOfStrings.Any(x => x == "Test3");
public bool EnableProperty4 => _listOfStrings.Any(x => x == "Test4");
public void AddToList(string value)
{
_listOfStrings.Add(financialProductType);
// Should I call the OnPropertyChanged here
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The easiest thing to do here would be to manually call OnPropertyChanged in the AddString method.
public void AddToList(string value)
{
_listOfStrings.Add(financialProductType);
OnPropertyChanged("EnableProperty1");
OnPropertyChanged("EnableProperty2");
// etc
}
This is fine if you're not likely to change the class much. If you add another property that's calculated from _listOfStrings you'll need to add a OnPropertyChanged call here.
Using an ObservableCollection doesn't really help because you already know when the list changes (AddToList) and you'll still have to trigger all the OnPropertyChanged methods anyway.
As far as I can see, there are 2 things you are missing in your implementation:
You should use ObservableCollection instead of List. As the name suggest, the former one can be observed (notify about its changing) by the view.
You need to bind a control to the public ObservableCollection and call OnPropertyChanged every time you assign/change value of the collection. something like this:
private ObservableCollection<string> _myList;
// your control should bind to this property
public ObservableCollection<string> MyList
{
get => return _myList;
set
{
// assign a new value to the list
_myList = value;
// notify view about the change
OnPropertiyChanged(nameof(MyList));
}
}
// some logic in your view model
string newValue = "newValue";
_myList.Add(newValue );
OnPropertyCHanged(nameof(MyList));
Hope this helps?
Related
I have a combo box that is bound to an object from a model that is instantiated inside of my view model. OnPropertyChange is handled inside of the Notifier class that inherits from INotifyPropertyChange. The view model polls executes a method from a data access layer and returns an observablelist to the view model. This is then passed into a constructor that builds the object i want to bind to the combo box. The object has two properties. 1) An observable list of possible selections and 2) a string that represents the current selected item.
Here is the problem. The combo box is successfully bound and populated by the list. It does not however appear to call the setter method when an item is changed. I need this functionality so I can continue with application logic once the item is elected. Debugging confirms that no setter is called, only the get. The setter is in fact called on the model however which make sense. Im certain I am missing something here and am open to suggestions on a better way to do this.
Model
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return _headers; }
set { _headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View Model
public class MainViewModel : Notifier
{
//data access layer
public static getWells gw = new getWells();
//set combo box
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = headers[0]};
public WellListGroup wlg
{
get {
return _wlg;
}
set {
_wlg = value;
OnPropertyChanged("wlg");
OnChange()// do stuff!!!
}
}
View
<ComboBox x:Name="groupComboBox"
DockPanel.Dock="Top"
ItemsSource = "{Binding Path = wlg.headers}"
SelectedItem="{Binding Path = wlg.selected, Mode=TwoWay}">
</ComboBox>
EDIT - Reworked ViewModel to Subscribe to event on the object
public class MainViewModel : Notifier
{
//data access layer
public static getWells gw = new getWells();
//set combo box
public static List<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = headers[0]};
public WellListGroup wlg
{
get {
return _wlg;
}
set {
_wlg = value;
OnPropertyChanged("wlg");
OnChange(_wlg.selected);// do stuff!!!
}
}
public MainViewModel()
{
// Move this into the constructor to avoid any race conditions
_wlg = new WellListGroup {headers = headers, selected = headers[0]};
// Subscribe to the property change even for WLG
_wlg.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "selected") {
}
OnChange(_wlg.selected);// do stuff!!!
};
}
The situation is that the reflected property setter is within the _wlg class and not the setter of the _wlg class itself on the VM. The bounded item is not going to the top level but the lower property as mentioned.
Either put in a commanding system to kick off the OnChange()// do stuff!!! code or subscribe to the _wlg class instance INotifyProptertyChanged event and call the method you mentioned.
Is there anyway to handle either of those from within the view model?
Yes, subscribe to the instance of the class WellListGroup property changed event and look for selected or others to report a change.
public MainViewModel()
{
// Move this into the constructor to avoid any race conditions
_wlg = new WellListGroup {headers = headers, selected = headers[0]};
// Subscribe to the property change even for WLG
_wlg.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == 'selected')
OnChange()// do stuff!!!
};
}
Of note, it is unclear if you really need to hold the strings in an ObservableCollection. That collection has its own implementation of notify events for adding and deleting of items within the collection.
If the VM needs that specific change info, then you will need to subscribe to the ObservableCollection's event(s) for such operations instead of/as well as the aforementioned above example.
If one does not need those notifications, holding the strings in an ObservableCollection is not needed and you can change it to a List<string> instead.
I was experimenting with Data Binding in Windows Forms and found a glitch that I can't explain. I post the question here in hopes that someone in the community can come up with an answer that makes sense.
I tried to come up with a clever way of binding read-only values that depend on operations on other values, and update it automatically when the dependent values change.
I created a form with 3 textboxes, where I want the sum of the first 2 to appear in the 3rd textbox.
The following code should work, but doesn't, at least not properly:
public class Model : INotifyPropertyChanged
{
private int m_valueA;
private int m_valueB;
public int ValueA
{
get { return m_valueA; }
set { m_valueA = value; RaisePropertyChanged("ValueA"); }
}
public int ValueB
{
get { return m_valueB; }
set { m_valueB = value; RaisePropertyChanged("ValueB"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DynamicBindingProperty<T> : INotifyPropertyChanged
{
private Func<T> m_function;
private HashSet<string> m_properties;
public DynamicBindingProperty(Func<T> function, INotifyPropertyChanged container, IEnumerable<string> properties)
{
m_function = function;
m_properties = new HashSet<string>(properties);
container.PropertyChanged += DynamicBindingProperty_PropertyChanged;
}
public T Property { get { return m_function(); } }
void DynamicBindingProperty_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!m_properties.Contains(e.PropertyName)) return;
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeDataBinding();
}
private void InitializeDataBinding()
{
Model model = new Model();
DynamicBindingProperty<int> tmp = new DynamicBindingProperty<int>(() => model.ValueA + model.ValueB, model, new[] {"ValueA", "ValueB"});
textBox1.DataBindings.Add("Text", model, "ValueA");
textBox2.DataBindings.Add("Text", model, "ValueB");
textBox3.DataBindings.Add("Text", tmp, "Property");
tmp.PropertyChanged += (sender, args) => Console.WriteLine(args.PropertyName);
}
}
After experimenting for a while, I tried renaming DynamicBindingProperty<T>.Property to something else (e.g. DynamicProperty), and everything worked as expected!. Now, I was expecting something to break by renaming Model.ValueA to Property, but it didn't, and still worked flawlessly.
What is going on here?
I did some debugging and it looks like a bug (or requirement "the property must not be named Property" I am not aware of). If you replace
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
with
PropertyChanged(this, new PropertyChangedEventArgs(null));
it still does not work - null or an empty string means any property may have changed. This indicates that problem is not in the handling of the change notification but that the binding has not been correctly established.
If you add a second property Property2 to DynamicBindingProperty<T> that does the same as Property and bind it to a fourth text box, then both text boxes will get update correctly if you perform a change notification with an empty string, null or "Property2". If you perform the change notification with "Property" both text boxes will not get update correctly. This indicates that the binding to Property is not completely broken and also that the change notification is somewhat broken.
Sadly I was unable to pin down the exact location where things go wrong, but if you invest enough time stepping through optimized framework source code you can probably figure it out. The earliest difference between the case with property name Property and the case with property name Property2 I could identify when processing a change notification was in OnValueChanged() in the internal class System.ComponentModel.ReflectPropertyDescriptor. In one case the base implementation gets called while it gets skipped in the other case - at least if the debugger didn't trick me, but this is hard to tell in optimized code.
Given a standard view model implementation, when a property changes, is there any way to determine the originator of the change? In other words, in the following view model, I would like the "sender" argument of the "PropertyChanged" event to be the actual object that called the Prop1 setter:
public class ViewModel : INotifyPropertyChanged
{
public double Prop1
{
get { return _prop1; }
set
{
if (_prop1 == value)
return;
_prop1 = value;
// here, can I determine the sender?
RaisePropertyChanged(propertyName: "Prop1", sender: this);
}
}
private double _prop1;
// TODO implement INotifyPropertyChanged
}
Alternatively, is it possible to apply CallerMemberNameAttribute to a property setter?
If I understood correctly, you're asking about the caller of the setter. That means, the previous method call in the call stack before getting to the setter itself (which is a method too).
Use StackTrace.GetFrames method for this. For example (taken from http://www.csharp-examples.net/reflection-callstack/):
using System.Diagnostics;
[STAThread]
public static void Main()
{
StackTrace stackTrace = new StackTrace(); // get call stack
StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames)
// write call stack method names
foreach (StackFrame stackFrame in stackFrames)
{
Console.WriteLine(stackFrame.GetMethod().Name); // write method name
}
}
The output:
Main
nExecuteAssembly
ExecuteAssembly
RunUsersAssembly
ThreadStart_Context
Run
ThreadStart
Basically, what you're asking for would be stackFrames[1].GetMethod().Name.
My first approach to your problem would be to derive from PropertyEventArgs. The new class would have a member called, for instance PropertyChangeOrigin in addition to PropertyName. When you invoke the RaisePropertyChanged, you supply an instance of the new class with the PropertyChangeOrigin set from the information gleaned from the CallerMemberName attribute. Now, when you subscribe to the event, the subscriber could try casting the eventargs to your new class and use the information if the cast is successful.
This is what I always use as a middle-ground between INotifyPropertyChanged and my View Models:
public class NotifyOnPropertyChanged : INotifyPropertyChanged
{
private IDictionary<string, PropertyChangedEventArgs> _arguments;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged([CallerMemberName] string property = "")
{
if(_arguments == null)
{
_arguments = new Dictionary<string, PropertyChangedEventArgs>();
}
if(!_arguments.ContainsKey(property))
{
_arguments.Add(property, new PropertyChangedEventArgs(property));
}
PropertyChanged(this, _arguments[property]);
}
}
Two things here. It uses the [CallerMemberName] attribute to set the property name. This makes the usage syntax as follows:
public string Words
{
set
{
if(value != _words)
{
_words = value;
OnPropertyChanged( );
}
}
}
Beyond that, it stores the PropertyChangedEventArgs object in a dictionary so it's not created a ton of times for properties that are frequently set. I believe this addresses your problem. Good luck!
Whenever I have had to pass in extra information down into a VM I have a great success with using commands:
Commands, RelayCommands and EventToCommand
I have a Class, MyClass that implements INotifyPropertyChanged and has some properties that implement PropertyChanged. When MyClass.MyProperty changes, PropertyChanged fires as expected. Another class contains a SortedList<MyClass> .I've tried merging the events into a single observable in the class that contains the SortedSet<MyClass> and subscribing to it, but it doesn't seem to ever have any events. Here's what I'm trying:
Observable.Merge(MySortedList.ToObservable())
.Subscribe(evt => Console.WriteLine("{0} changed", evt.MyProperty));
What I'm trying to get is a single observable that contains all of the events from every item in my SortedList<MyClass>. I've tried using ObservableCollection instead, but that doesn't change anything, nor would it be expected to, really, since it doesn't fire collectionchanged when a property of a contained item changes, anyway. I can listen to individual elements in SortedList<MyClass> and see the PropertyChanged event fire, but what I want is a single Observable that contains a stream of ALL of the PropertyChanged events from all of the elements in SortedList<MyClass>.
It seems like this should be something fairly easy to do using Rx, but I can't seem to figure out how.
I have produced an article for the RxCookBook on this subject that you can find here
https://github.com/LeeCampbell/RxCookbook/blob/master/Model/CollectionChange.md
Further article on PropertyChange notification is here https://github.com/LeeCampbell/RxCookbook/blob/master/Model/PropertyChange.md
It solves what you need by aggregating up the changes from an ObservableCollection<T>. By using the ObservableCollection<T> you also get notifications when items are added or removed from the collection.
If you dont want to use the ObservableCollection<T> (i.e. you only want to track properties at a given snapshot of the collection) then you will need to do something else. First I assume you have an INoftifyPropertyChanged to IObservable<T> extension method or you are just going to use the standard event to IObservable<T> methods.
Next you can project the List of values into a list of change sequences i.e. IEnumerable<T> to IEumerable<IObserable<T>>. This allows you to use Observable.Merge to flatten the list of changes in to a single stream of changes.
Here is a sample if you dont want to use the link above:
void Main()
{
var myList = new List<MyThing>{
new MyThing{Name="Lee", Age=31},
new MyThing{Name="Dave", Age=37},
new MyThing{Name="Erik", Age=44},
new MyThing{Name="Bart", Age=24},
new MyThing{Name="James", Age=32},
};
var subscription = Observable.Merge(myList.Select(t=>t.OnAnyPropertyChanges()))
.Subscribe(x=>Console.WriteLine("{0} is {1}", x.Name, x.Age));
myList[0].Age = 33;
myList[3].Name = "Bob";
subscription.Dispose();
}
// Define other methods and classes here
public class MyThing : INotifyPropertyChanged
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public int Age
{
get { return _age; }
set
{
_age = value;
OnPropertyChanged("Age");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public static class NotificationExtensions
{
/// <summary>
/// Returns an observable sequence of the source any time the <c>PropertyChanged</c> event is raised.
/// </summary>
/// <typeparam name="T">The type of the source object. Type must implement <seealso cref="INotifyPropertyChanged"/>.</typeparam>
/// <param name="source">The object to observe property changes on.</param>
/// <returns>Returns an observable sequence of the value of the source when ever the <c>PropertyChanged</c> event is raised.</returns>
public static IObservable<T> OnAnyPropertyChanges<T>(this T source)
where T : INotifyPropertyChanged
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
handler => handler.Invoke,
h => source.PropertyChanged += h,
h => source.PropertyChanged -= h)
.Select(_=>source);
}
}
Which will output:
Lee is 33
Bob is 24
Assuming from your description that you're able to provide this:
IEnumerable<KeyValuePair<string, IObservable<object>>> observableProperties;
You can use this to merge the property observables into a single observable:
IObservable<KeyValuePair<string, object>> changes = observableProperties
.Select(p => p.Value
.Select(o => new KeyValuePair<string, object>(p.Key, o)))
.Merge();
If you find this insufficient, you'll need to give more details about why you can't provide observableProperties.
For example, if I had a class like this:
class MyClass
{
public IObservable<int> X { get { ... } }
public IObservable<string> S { get { ... } }
}
I could create observableProperties like this:
var myObject = new MyClass();
var observableProperties = new []
{
new KeyValuePair<string, IObservable<object>>("X", myObject.X.Cast<object>()),
new KeyValuePair<string, IObservable<object>>("S", myObject.S.Cast<object>())
};
I have following class which I use for radio button binding
public class RadioButtonSwitch : ViewModelBase
{
IDictionary<string, bool> _options;
public RadioButtonSwitch(IDictionary<string, bool> options)
{
this._options = options;
}
public bool this[string a]
{
get
{
return _options[a];
}
set
{
if (value)
{
var other = _options.Where(p => p.Key != a).Select(p => p.Key).ToArray();
foreach (string key in other)
_options[key] = false;
_options[a] = true;
RaisePropertyChanged("XXXX");
else
_options[a] = false;
}
}
}
XAML
<RadioButton Content="Day" IsChecked="{Binding RadioSwitch[radio1], Mode=TwoWay}" GroupName="Monthly" HorizontalAlignment="Left" VerticalAlignment="Center" />
ViewModel
RadioSwitch = new RadioButtonSwitch(
new Dictionary<string, bool> {{"radio1", true},{"radio2", false}}
);
I'm having problem with RaisePropertyChanged() in my Class. I'm not sure what value I should be putting in order to raise the change.
I tried putting:
Item[]
a
[a]
I keep getting following error:
This is so in case of any change I can handle it in my view accordingly. Please do not give me solutions for List for radio buttons etc.
The problem is that you are implementing an indexer, not an ordinary property. Although the binding subsystem supports indexers, MVVMLight and INotifyPropertyChanged do not.
If you want to use an indexer you need to:
Use a collection base class such as ObservableCollection<T>
Implement INotifiyCollectionChanged and raise that event instead
The first option isn't realistic because you are already deriving from ViewModelBase and have to continue to do that. Since implementing INotifiyCollectionChanged is a little bit of work, the easiest approach is to:
add a property to RadioButtonSwitch that is an observable collection of boolean values (ObservableCollection<bool>)
Then change you binding to add one more path element and you are done.
Edit:
Based on your comment and rereading your question, I think implementing INotifyCollectionChanged is the easiest. Here is the rewrite of your RadioButtonSwitch class which actually no longer needs to derive from the MVVMLight base class, although you still could if you wanted to.
The careful reader will notice that we use a sledgehammer and "reset" the whole collection when any element of the collection is modified. This is not just laziness; it is because the indexer uses a string index instead of an integer index and INotifyCollectionChanged doesn't support that. As a result, when anything changes we just throw up our hands and say the whole collection has changed.
public class RadioButtonSwitch : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void RaiseCollectionChanged()
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
IDictionary<string, bool> _options;
public RadioButtonSwitch(IDictionary<string, bool> options)
{
this._options = options;
}
public bool this[string a]
{
get
{
return _options[a];
}
set
{
if (value)
{
var other = _options.Where(p => p.Key != a).Select(p => p.Key).ToArray();
foreach (string key in other)
_options[key] = false;
_options[a] = true;
RaiseCollectionChanged();
}
else
_options[a] = false;
}
}
}
GalaSoft.MvvmLight has the following code to check property name before raising PropertyChanged event.
public void VerifyPropertyName(string propertyName)
{
if (GetType().GetProperty(propertyName) == null)
throw new ArgumentException("Property not found", propertyName);
}
GetType().GetProperty("Item[]") obviously returns null.
This is why it is failing.
I think, the quickest workaround for you would be not to use ViewModelBase from this library, but implement your own version, that doesn't do this check:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
If you implement this class you will be able to run RaisePropertyChanged("Item[]").