Updating UI Bound to Dependant Properties - c#

I have several properties that return values dependent on another property's value. What is the best way to update the UI bound to a dependent property? See example below, how would you update a UI element bound to the TotalQuantity property of the Parent object when a Quantity in the Children collection changes?
public class Child : INotifyPropertyChanged
{
private int quantity;
public int Quantity
{
get
{
return quantity;
}
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
public class ParentObject : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
public ObservableCollection<Child> Children
{
get
{
return children;
}
set
{
children = value;
OnPropertyChanged("Children");
}
}
public int TotalQuantity
{
get
{
return Children.Sum(c => c.Quantity);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}

When you instantiate your ObservableCollection, you need to subscribe to the CollectionChanged event.
Children.CollectionChanged += Children_CollectionChanged;
This will be called when an item is added/removed from the collection. You simply need to notify that TotalQuantity has changed.
void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("TotalQuantity");
}
In the case where you need to update the TotalQuantity property on the UI whenever a child changes, then you simply need to subscribe to the child's PropertyChanged event.
So, when you add an item to the collection, subscribe to the event:
Child myChild = new Child(); //Just an example, but you get the idea
myChild.PropertyChanged += myChild_PropertyChanged;
And in your event handler, you can test to see what property has changed.
void myChild_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Quantity")
OnPropertyChanged("TotalQuantity");
}
Or, you could just call OnPropertyChanged without checking if you're a badass. But I wouldn't recommend it just in case your model gains more properties in future.
Don't forget to unsubscribe the event before you remove the child from the collection.

Related

Why does raising the PropertyChanged event cause other controls to update

I have a view model that has several properties that are databound to several controls.
When I raise PropertyChanged on one of them, the controls unexpectedly all update. I would expect only the one I am raising the event on to update.
For my form, I have this:
public partial class MainForm : Form
{
AmountCalculatorVM amountCalculatorVM;
public MainForm()
{
InitializeComponent();
}
private void setBindings()
{
textBoxTotalAmount.DataBindings.Add("Text", amountCalculatorVM, "TotalAmount");
textBoxAverage.DataBindings.Add("Text", amountCalculatorVM, "Average",true, DataSourceUpdateMode.Never,null, "#.00");
textBoxCount.DataBindings.Add("Text", amountCalculatorVM, "Count");
listBoxLineAmounts.DataSource = amountCalculatorVM.Amounts;
}
private void MainForm_Load(object sender, EventArgs e)
{
amountCalculatorVM = new AmountCalculatorVM();
setBindings();
}
private void buttonAddAmount_Click(object sender, EventArgs e)
{
if (int.TryParse(textBoxLineAmount.Text.Replace(",", ""), out int amount))
{
amountCalculatorVM.Amounts.Add(amount);
textBoxLineAmount.Text = "";
textBoxLineAmount.Focus();
}
}
private void buttonClear_Click(object sender, EventArgs e)
{
textBoxLineAmount.Text = "";
amountCalculatorVM.Amounts.Clear();
textBoxLineAmount.Focus();
}
}
Then, for my view model, I have this:
class AmountCalculatorVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly AmountList amounts;
public BindingSource Amounts { get; }
public int TotalAmount => amounts.TotalAmount;
public int Count => amounts.Count;
public decimal Average => amounts.Average;
public AmountCalculatorVM()
{
amounts = new AmountList();
Amounts = new BindingSource();
Amounts.DataSource = amounts;
Amounts.ListChanged += Amounts_ListChanged;
}
private void Amounts_ListChanged(object sender, ListChangedEventArgs e)
{
//Any one of these will cause all three textboxes to update in the form
//I would think that with Count and Average commented out, the Count and
//Average textboxes would not update.
OnPropertyChanged("TotalAmount");
//OnPropertyChanged("Count");
//OnPropertyChanged("Average");
//Using any other word will not
//OnPropertyChanged("SomeOtherRandomWord");
}
}
Here is the AmountList class for reference:
class AmountList : List<int>
{
public int TotalAmount
{
get
{
int total = 0;
foreach (int amount in this)
{
total += amount;
}
return total;
}
}
Now, unexpectedly, all three textboxes update if an item is added to the amounts list, which fires ListChanged, and then in turn, the PropertyChanged event.
It doesn't matter which of the three properties I fire PropertyChanged on, but it won't work if I use a different value - it needs to be either TotalAmount, Count, or Average.
I can't understand this behaviour. I would have expected only the text box bound to TotalAmount to be updated, and not the other two, since nothing seems to be notifying them that an update has occurred.
Any ideas?
Why don't you implement the propertychanged like this:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
You can control now, in the setter, which property fires the event:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
you know what I mean?

Bind to Collection Count Where

My question is similar to this one: Bind to Count of List where Typeof
But how does this work for Classes?
In my MainWindow I have the following Collection and a Selected Count Property
private ObservableCollection<MyClass> _myClassCollection = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass>
{
get => _myClassCollection;
set
{
if(_myClassCollection == value) return;
_myClassCollection = value;
OnPropertyChanged("MyClassCollection");
}
}
public int SelectedCount
{
get => MyClassCollection.Where(x => x.IsSelected == true).Count();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
My MyClass:
public class MyClass : INotifyPropertyChanged
{
// .. Properties
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if(_isSelected == value) return;
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
So how can I "run" the SelectedCount Property, if the IsSelected Property of MyClass changed ? I want to show the number of Selected Items of the ObservableCollection in real time.
You can just add an OnPropertyChaned for SelectedCountin the setter of the other operations where a change may have occurred. For instance on setting the my class collection. This will tell stuff listening to that particular property something may have changed, get the value again.
set
{
if(_myClassCollection == value) return;
_myClassCollection = value;
OnPropertyChanged("MyClassCollection");
OnPropertyChanged("SelectedCount"); // Make the change Here.
}
From the comments it would seem you need to listen to each element's property changed explicitly. Here is an example of how that would look in your setter with an event handler.
set
{
// remove subscriptions
if(_myClassCollection != null)
{
foreach(var element in _myClassCollection)
{
element.PropertyChanged -= ElementChanged;
}
}
// set to new collection
_myClassCollection = value;
// subscribe to new elements.
if(_myClassCollection != null)
{
foreach(var element in _myClassCollection)
{
element.PropertyChanged += ElementChanged;
}
}
OnPropertyChanged("MyClassCollection");
OnPropertyChanged("SelectedCount"); // Make the change Here.
}
private void ElementChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(MyClass.IsSelected))
{
OnPropertyChanged("SelectedCount");
}
}
Now if you are adding or removing elements from your collection without creating a new collection, you will need to subscribe or remove subscriptions inside a CollectionChanged event handler.
You can use DynamicData to make it more readable:
var allObjects = new SourceList<MyClass>(); // this is what you populate with your objects
SelectedObjects = allObjects.Connect().Filter(x => x.IsSelected).AsObservableList();
If SelectedObjects is a public property, you can bind like:
<TextBloc Text="{Binding SelectedObjects.Count}"/>
You have to handle the CollectionChanged from your ObservableCollection.
There you have to call OnPropertyChanged("SelectedCount") like in the linked Question.
In your set:
_myClassCollection.CollectionChanged += Handle_CollectionChanged;
In the event handler:
private void Handle_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("SelectedCount");
}

INotifyPropertyChanged bubbling in class hierarchy

Suppose, I have the following class hierarchy:
public NestedClass : INotifyPropertyChanged {
string prop1;
public String Prop1 {
get { return prop1; }
set {
prop1 = value;
OnPropertyChanged("Prop1");
}
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public Class1 : INotifyPropertyChanged {
ObservableCollection<NestedClass> collection;
public Class1() {
collection = new ObservableCollection<NestedClass>();
}
public ObservableCollection<NestedClass> Collection {
get { return collection; }
set {
if (collection != null) {
collection.CollectionChanged -= childOnCollectionChanged;
}
collection = value;
if (collection != null) {
collection.CollectionChanged += childOnCollectionChanged;
}
}
}
void childOnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
((INotifyPropertyChanged)e.NewItems[0]).PropertyChanged += childOnPropertyChanged;
break;
case NotifyCollectionChangedAction.Remove:
((INotifyPropertyChanged)e.OldItems[0]).PropertyChanged -= childOnPropertyChanged;
break;
case NotifyCollectionChangedAction.Reset:
if (e.OldItems == null) { break; }
foreach (INotifyPropertyChanged itemToRemove in e.OldItems) {
itemToRemove.PropertyChanged -= childOnPropertyChanged;
}
break;
}
}
void childOnPropertyChanged(Object sender, PropertyChangedEventArgs e) {
OnPropertyChanged("nested");
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
there is class Class1 which contains some properties and several collections of different classes. All collections are defined in the same was as a collection of NestedClass. They are properly bound to the UI, I have no problems there.
Class1 object and nested objects/collections can be created at runtime, or can be generated by a XML deserializer. To properly handle deserialized collection, I have to subscribe to INotifyPropertyChanged event of each collection item when collection is assigned.
My goal is to handle all changes of all items of NestedClass in Class1 to get information that data was changed (and pass this information up to its parent).
The problem is that I have mutiple nested collections and they notify parent class Class1 and it successfully bubbles the event to its parent. However, one collection notifies parent Class1 about changes, but handler in the Class1 is null, as the result Class1 doesn't bubble event to its parent class (is not shown here as irrelevant). I went through debugger but was unable to find where is the problem. When other nested collections call parent's OnPropertyChanged, the handler is not null and everything is working correctly.
Edit: this issue is raised only when Class1 and NestedClass objects are generated by XML deserializer.
I read a lot of similar posts on SO, but most of them are about invalid DataContext in the view, which is not my case (I believe). What else I can check to find the root of the issue?
You might want to consider switching to a BindingList<T> instead of a ObserveableCollection<T>, it has the logic you do in childOnCollectionChanged already built in to it. Just make sure RaiseListChangedEvents is set to true and the ListChanged event will be raised with args.ListChangedType == ListChangedType.ItemChanged any time one of the members raises its PropertyChanged event.

How to notify a bound element oh a value change when value derives from model?

I have the visibility of a progress bar bound to The following property within my viewmodel:
public string CalcProgVisibility
{
get
{
return Calculation.CalcProgVisibility;
}
set
{
}
}
Calculation is my model, which can change the value. When the value changes within the model, what do I need to do to make sure the view is aware of this change?
EDIT:
Here is the property within my model too. I am using onpropertychanged but its not making it to the view.
I am changing the value within the model, the view is bound to my viewmodel and the viewmodel si trying to return a value taken from the model. I am updating the value on the model, and cannot push the fact that it has updated the value all the way down to the view, I can only get the viewmodel to see it has changed...
I updated the entire code. I hope it's clear now.
Define your control BindingMode = TwoWay
<TextBox Visibility="{Binding Path=CalcProgVisibility, Mode=TwoWay}"...
and call the OnPropertyChanged method on the setter of the property in your view model and also in your model
//Model
public class Calculation : INotifyPropertyChanged
{
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Calculation model)
{
this.CalcProgVisibility = model.CalcProgVisibility;
model.PropertyChanged += (s, e) => UpdateEntity(s as Calculation);
}
private void UpdateEntity(Calculation source)
{
CalcProgVisibility = source.CalcProgVisibility;
}
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Your Viewmodel has to implement the INotifyPropertyChanged Interface. To fire it in your case your viewmodel must also be aware of changes in your model object. So your model object could also implement INotifyPropertyChanged, or you use some form of the observer pattern.
If your model implements INotifyPropertyChanged, your viewmodel must manually register for this event and implement an handler. This could in turn trigger the PropertyChange event of the viewmodel then.
Another but in my opinion ugly way would be to scan (per timer or background thread) through your viemodel and check if a value changed since the last scan and then trigger a property changed event.
The first solution could look like this:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StackOverflow
{
[TestClass]
public class IntegrationTest
{
[TestMethod]
public void NotifyPropertyChangeShouldFireOnViewModelWhenModelChanges()
{
//Arrange
Model model = new Model();
ViewModel sut = new ViewModel(model);
bool notifyPropertyChangeOnViewModelWasCalled = false;
sut.PropertyChanged += (sender, e) => { notifyPropertyChangeOnViewModelWasCalled = true; };
//Act
model.CalcValue = 4711;
//Assert
Assert.IsTrue(notifyPropertyChangeOnViewModelWasCalled, "NotifyPropertyChange was not fired on ViewModel");
}
}
public class ObjectWithNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model : ObjectWithNotifyPropertyChanged
{
private double calcValue;
public double CalcValue
{
get
{
return calcValue;
}
set
{
if (calcValue != value)
{
calcValue = value;
RaisePropertyChanged();
}
}
}
}
public class ViewModel : ObjectWithNotifyPropertyChanged
{
public ViewModel(Model model)
{
this.model = model;
model.PropertyChanged += model_PropertyChanged;
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CalcValue":
RaisePropertyChanged("CalcValue");
break;
}
}
private Model model;
public double CalcValue
{
get
{
return model.CalcValue;
}
}
}
}

Creating a custom event that captures when a value has changed

I have this small problem. I want to capture every time a property is changed.
This property is wrapped inside another user control:
var color = (CustomWPFColorPicker.ColorPickerControlView) elementHost1.Child;
color.CurrentColor <--This property.
How can I detect when the CurrentColor property has changed?
Implement INotifyPropertyChanged on your custom control and raise the PropertyChanged event when the given properties change.
The consumer can then register for the PropertyChanged event and check the property which raised the event to see if it is the property they care about.
public class MyControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private Color _color = null;
public Color CurrentColor
{
get
{
return _color;
}
set
{
_color = value;
NotifyPropertyChanged("CurrentColor");
}
}
}
Then the consumer can register for the event and check the property as needed...
MyControl control = new MyControl();
control.PropertyChanged += OnPropertyChanged;
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentColor")
{
//do stuff...
}
}
Try to check if ColorPickerControlView implements INotifyPropertyChanged. If it does attach an handler to PropertyChanged event inside ColorPickerControlView and then check if the property that has changed is CurrentColor:
void myControlPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentColor")
{
// Do something
}
}
EDIT: If you can modify the custom control try to implement INotifyPropertyChanged. Hope this example helps:
Color currentColor;
public Color CurrentColor
{
get { return currentColor; }
set { currentColor = value; OnPropertyChanged("CurrentColor"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Categories

Resources