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");
}
Related
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.
I have an Item class with two properties "quantity" and "price" and implements INotifyPropertyChanged
public class Item:INotifyPropertyChanged
{
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int QuantityOnHand
{
get
{
return this._quantityOnHand;
}
set
{
if (value > 0)
{
this._quantityOnHand = value;
NotifyPropertyChanged();
}
else
{
throw new System.ArgumentException("Quantity must be a positive value!");
}
}
}
.....
}
And I have a collection class of items named "Inventory" with a property of TotalRetailPrice:
public class Inventory {
private List<Item> _inventoryList = new LinkedList<Item>();
public decimal TotalRetailPrice
{
get
{
decimal totalRetailPrice = 0M;
foreach (var item in _inventoryList)
{
totalRetailPrice += item.QuantityOnHand * item.RetailPrice;
}
return totalRetailPrice;
}
}
I am trying to find out a way to automatically update this property TotalRetailPrice, whenever I change either the quantity or the price of any item(s) in the list. How can I do that? Right now with my code, every time I tried to get this totalRetailPrice property, I will have to go through the list and recalculate it.
thanks!
Since the interface INotifyPropertyChanged exposes an event called PropertyChanged you can just subscribe to that in the 'inventory' class.
You will also want to listen for changed events in the list since you will need to know when items are added/removed so you can add/remove event handlers as necessary. I'd suggest using ObservableCollection<T> as this supports some 'collection changed' events. Is there any reason you are using LinkedList<T>?
e.g.
public class Inventory
{
private ObservableCollection<Item> _inventoryList = new ObservableCollection<Item>();
public decimal _total;
// You probably want INPC for the property here too so it can update any UI elements bound to it
public decimal Total { get { return _total; } set { _total = value; } }
// Constructor
public Inventory()
{
WireUpCollection();
}
private void WireUpCollection()
{
// Listen for change events
_inventoryList.CollectionChanged += CollectionChanged;
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Check what was added - I'll leave this to you, the e.NewItems are the items that
// have been added, e.OldItems are those that have been removed
// Here's a contrived example for when an item is added.
// Don't forget to also remove event handlers using inpc.PropertyChanged -= Collection_PropertyChanged;
var inpc = e.NewItems[0] as INotifyPropertyChanged;
if(inpc != null)
inpc.PropertyChanged += Collection_PropertyChanged;
}
private void Collection_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RecalculateTotal();
}
private void RecalculateTotal()
{
// Your original code here which should feed the backing field
}
}
Check out the MSDN docs here:
http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
For info on ObservableCollection<T>. The events section is what you are after. Also note you can use anonymous functions to handle the events if you prefer the syntax or want to capture variables in a certain scope etc. It helps to understand them fully (not sure what's available for Java as I've not really touched it save a couple of Android mess-about projects) so it might be worth reading up as there are a small caveats to be aware of when capturing, but that's another story!
e.g.
_inventoryList.CollectionChanged += (o,e) =>
{
// Anonymous method body here
// o = the first param (object sender), e = args (NotifyCollectionChangedEventArgs e)
};
As it was told in another answer in order to "observe" collection items you have to use ObservableCollection which has a special event CollectionChanged. It is raised when the list is changed somehow (item added, removed, replaced). However, that event won't be raised (obviously) when some property of the existing item is changed, for example, inventory.InventoryList[0].QuantityOnHand = 8;.
So, to get the solution worked you need to observe both the collection changes (CollectionChanged event) and each collection item changes (PropertyChanged event). Though, implementing that logic correctly is not so easy. The Charlen answer is just a sketch of the solution but not the full solution.
It might be easier to use DependenciesTracking lib which solves the issue. See the example below:
public class Item : INotifyPropertyChanged
{
private int _quantityOnHand;
private decimal _retailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public int QuantityOnHand
{
get => this._quantityOnHand;
set
{
if (_quantityOnHand == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "QuantityOnHand must be a positive value");
_quantityOnHand = value;
OnPropertyChanged();
}
}
public decimal RetailPrice
{
get => _retailPrice;
set
{
if (_retailPrice == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "RetailPrice must be a positive value");
_retailPrice = value;
OnPropertyChanged();
}
}
}
public class Inventory : INotifyPropertyChanged
{
private static readonly IDependenciesMap<Inventory> _dependenciesMap =
new DependenciesMap<Inventory>()
.AddDependency(i => i.TotalRetailPrice,
i => i.InventoryList?.Sum(item => item.QuantityOnHand * item.RetailPrice) ?? 0.0m,
i => i.InventoryList.EachElement().QuantityOnHand, i => i.InventoryList.EachElement().RetailPrice);
private ObservableCollection<Item>? _inventoryList;
private decimal _totalRetailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public ObservableCollection<Item>? InventoryList
{
get => _inventoryList;
set
{
if (_inventoryList == value) return;
_inventoryList = value;
OnPropertyChanged();
}
}
public decimal TotalRetailPrice
{
get => _totalRetailPrice;
private set
{
if (value == _totalRetailPrice) return;
_totalRetailPrice = value;
OnPropertyChanged();
}
}
public Inventory()
{
_dependenciesMap.StartTracking(this);
}
}
public class Tests_SO_20767981
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test_SO_20767981()
{
var inventory = new Inventory();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList = new ObservableCollection<Item>();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList.Add(new Item { QuantityOnHand = 3, RetailPrice = 5 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(15));
inventory.InventoryList.Add(new Item { QuantityOnHand = 1, RetailPrice = 7 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(22));
inventory.InventoryList[0].QuantityOnHand = 8;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(47));
inventory.InventoryList[0].RetailPrice = 12;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(103));
inventory.InventoryList.RemoveAt(1);
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(96));
var newInventoryList = new ObservableCollection<Item>
{
new Item() { QuantityOnHand = 10, RetailPrice = 0.5m},
new Item() { QuantityOnHand = 6, RetailPrice = 1.5m}
};
inventory.InventoryList = newInventoryList;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(14m));
}
}
For an application I have to use a custom button that react when one of its properties value is being changed. I addded a field named Data to the new button:
public class ButtonData
{
public string Name;
public string Color;
//And more stuff...
}
Then I have the folowing code for new button, I want it to be able to update itself (change background color and some other stuff) whenever its Data property gets updated from somewhere in application. I found some ideas about implementing INotifyPropertyChanged interface and I set it up in my custom button like this:
public partial class ButtonPin : Button, INotifyPropertyChanged
{
private ButtonData _data;
public ButtonData Data
{
get { return _data; }
set
{
if (value == _data) return;
_data = value;
OnPropertyChanged("Data");
}
}
private bool _buttonDataAdded;
public ButtonPin()
{
InitializeComponent();
}
public ButtonPin(ButtonData data)
{
Data = data;
_buttonDataAdded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now I am not sure how to use this! For example, if the Color in Data objects gets changed
somehow somewhere and its get assigned to current button's Data field, I want this button to change its background color. Something like
var data = new ButtonData();
data.Name = "Hi!";
data.Color = Color.Red;
buttonPin1.Data = data; //Here I need the changes to occur
You have implemented the interface INotifyPropertyChanged on the ButtonPin class, not on the ButtonData class, and you want to detect a change on an object of type ButtonData, thus you need to implement the interface INotifyPropertyChanged on the ButtonData class.
To detect the change you need to hook up to the PropertyChanged event of the ButtonData object in the setter of the ButtonPin.Data property. Something like this.
private bool _data;
public ButtonData Data {
get { return _data; }
set {
if (value != _data) {
// Unhook previous events
if (_data != null)
_data.PropertyChanged -= HandleButtonDataPropertyChanged;
// Set private field
_data = value;
// Hook new events
if (_data != null)
_data.PropertyChanged += HandleButtonDataPropertyChanged;
// Immediate update since we have a new ButtonData object
if (_data != null)
Update();
}
}
}
private void HandleButtonDataPropertyChanged(object sender, PropertyChangedEventArgs e) {
// Handle change in ButtonData
Update();
}
private void Update() {
// Update...
}
This is an example code:
public class MyParent : INotifyPropertyChanged
{
List<MyChild> MyChildren;
public bool IsChanged
{
get
{
foreach (var child in MyChildren)
{
if (child.IsChanged) return true;
}
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyChild : INotifyPropertyChanged
{
private int _Value;
public int Value
{
get
{
return _Value;
}
set
{
if (_Value == value)
return;
_Value = value;
RaiseChanged("Value");
RaiseChanged("IsChanged");
}
}
private int _DefaultValue;
public int DefaultValue
{
get
{
return _DefaultValue;
}
set
{
if (_DefaultValue == value)
return;
_DefaultValue = value;
RaiseChanged("DefaultValue");
RaiseChanged("IsChanged");
}
}
public bool IsChanged
{
get
{
return (Value != DefaultValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
Let's say I now have two instances of my classes, one as myParent, and the other as myChild.
I have two visual elements, that each have a property bound to the IsChnaged property of my instances; ElementA bound to myParent.IsChanged and ElementB bound to myChild.IsChanged.
When myChild.Value differs from its default value, the myChild.IsChanged is set to true and the ElementB is updated accordingly.
What I need is when either of the myParent children (which here is only one) have their IsChanged value set to true, its own (the parent's) IsChanged value be set to true and its corresponding
element (ElementA here) be updated accordingly.
The myParent.IsChanged is only read once (when the binding is set) and it has no sense about its children changing. Where should i put the RaiseChanged("IsChanged") for MyParent? How can I let the parent know when its children have changed?
Thanks in advance
INotifyPropertyChanged has already provided the mechanism for you: the PropertyChanged event. Just have the parent add a handler to its children's PropertyChanged, and then in that handler call RaiseChanged("IsChanged");
Also, you may want to put the INotifyPropertyChanged implementation in a base class, and have your (what appear to be) ViewModels inherit from that. Not required for this option, of course, but it will make the code a little cleaner.
Update: In the parent object:
// This list tracks the handlers, so you can
// remove them if you're no longer interested in receiving notifications.
// It can be ommitted if you prefer.
List<EventHandler<PropertyChangedEventArgs>> changedHandlers =
new List<EventHandler<PropertyChangedEventArgs>>();
// Call this method to add children to the parent
public void AddChild(MyChild newChild)
{
// Omitted: error checking, and ensuring newChild isn't already in the list
this.MyChildren.Add(newChild);
EventHandler<PropertyChangedEventArgs> eh =
new EventHandler<PropertyChangedEventArgs>(ChildChanged);
newChild.PropertyChanged += eh;
this.changedHandlers.Add(eh);
}
public void ChildChanged(object sender, PropertyChangedEventArgs e)
{
MyChild child = sender as MyChild;
if (this.MyChildren.Contains(child))
{
RaiseChanged("IsChanged");
}
}
You don't actually have to add anything to the child class, since it is already raising the correct event when it changes.
Doing this kind of communication can be tricky, especially if you want to avoid memory leaks due to the event handlers that you hook up. There is also the case of handling items that are added / removed from the collection.
I've really enjoyed the power and simplicity of the Continuous LINQ project on codeplex. It has some very rich features for setting up "Reactive Objects", "Continuous Values", and "Continuous Collections". These let you define your criteria as a Linq expression and then let the CLINQ library keep the underlying values up to date in real time.
In your case, you could set up the parent with a ContinuousFirstOrDefault() linq query that watched for any child where "IsChanged == true". As soon as a child sets the value to true and raises PropertyChanged, the continuous value will detect the change and raise a corresponding PropertyChanged in the parent.
The benefits:
Weak references and weak events are used to prevent the event handlers in the parent from locking the child in memory. It can get very messy to add / remove these handlers from all the children.
You can declare the dependency in the parent without need to make special changes in the child or make the child aware of the parent. Rather, the child just needs to properly implement INotifyPropertyChanged. This puts the "logic" close to the object that cares, rather than spreading event craziness and inter-dependencies all over the code.
Here's what the code might look like:
public class MyParent : INotifyPropertyChanged
{
private ObservableCollection<MyChild> _MyChildren;
private ContinuousValue<MyChild> _ContinuousIsChanged = null;
public MyParent()
{
_MyChildren = new ObservableCollection<MyChild>();
// Creat the ContinuousFirstOrDefault to watch the MyChildren collection.
// This will monitor for newly added instances,
// as well as changes to the "IsChanged" property on
// instances already in the collection.
_ContinuousIsChanged = MyChildren.ContinuousFirstOrDefault(child => child.IsChanged);
_ContinuousIsChanged.PropertyChanged += (s, e) => RaiseChanged("IsChanged");
}
public ObservableCollection<MyChild> MyChildren
{
get { return _MyChildren; }
}
public bool IsChanged
{
get
{
// If there is at least one child that matches the
// above expression, then something has changed.
if (_ContinuousIsChanged.Value != null)
return true;
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyChild : INotifyPropertyChanged
{
private int _Value;
public int Value
{
get
{
return _Value;
}
set
{
if (_Value == value)
return;
_Value = value;
RaiseChanged("Value");
RaiseChanged("IsChanged");
}
}
private int _DefaultValue;
public int DefaultValue
{
get
{
return _DefaultValue;
}
set
{
if (_DefaultValue == value)
return;
_DefaultValue = value;
RaiseChanged("DefaultValue");
RaiseChanged("IsChanged");
}
}
public bool IsChanged
{
get
{
return (Value != DefaultValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
The above code sets up the ContinuousFirstOrDefault in the constructor so that it is always monitoring. However, in some cases you can optimize this by lazily instantiating the ContinuousFirstOrDefault only when the getter for "IsChanged" is called. That way you don't start monitoring for changes until you know that some other piece of code actually cares.
You can simplify things for yourself by storing your children in an ItemObservableCollection<T>, as discussed in this answer. That would allow you to do this:
private ItemObservableCollection<MyChild> children;
public MyParent()
{
this.children = new ItemObservableCollection<MyChild>();
this.children.ItemPropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (string.Equals("IsChanged", e.PropertyName, StringComparison.Ordinal))
{
this.RaisePropertyChanged("IsChanged");
}
};
}
Something I do not see in your code sample provide is an actually reference of parent to child. It is not enough to simply have interface to communicate through, but you must also create the reference. Something like myChild.parent = this; followed by the binding of the event handlers across the channel, in the "parent" property of the child object it would look like:
public INotifyPropertyChanged parent
{
get{return _parent;}
set
{
_parent = value;
this.PropertyChanged += _parent.RaiseChanged();
}
}
I don't have enough context to perfect this code for you but this should move you in the right direction.
I have a WPF dialog that is bound to a list of ObservableCollection<MyEntity> type. In the dialog, I want the "OK" button to be enabled only if changes are made to the ObservableCollection<MyEntity> list - that includes adding/removing items from the list and modifying the individual items in the list.
For adding/removing items from the list, it is easy - I implemented a handler for the CollectionChanged event.
What I don't know how to do is when an individual item is modified. Say, MyEntity.Name="New Value", what interface does MyEntity class need to implement to make it 'observable'?
MyEntity needs to implement INotifyPropertyChanged, then when a property change occurs you fire the PropertyChanged event. Like this:
public class MyEntity : INotifyPropertyChanged
{
public bool MyFlag
{
get { return _myFlag; }
set
{
_myFlag = value;
OnPropertyChanged("MyFlag");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Two ways to approach this are:
have an event listener internal to the object which then sets an IsDirty flag whenever a property changes. Then OK button is bound to a command (check out the usage of the ICommand interface), and in the CanExecute method of the command you check if any of the objects in the ObservableCollection have been set to dirty. This check can be done with a simple LINQ statement: myCollection.Any(x => x.IsDirty == true)
this method is more clunky and smelly.... have an external object listening for changes (by subscribing to the PropertyChanged event on each object), and that external listener can then enable the OK button (via databinding or by setting it directly).
I like the answer provided by slugster, here is an alternative building on slugster's answer.
If you bind to your OK button using DelegateCommnd you can add event handlers for CollectionChanged and PropertyChanged to change a simple boolean flag to control the state of the OK button.
public class MainViewModel : ViewModelBase
{
public DelegateCommand<object> RunCommand { get; set; }
public DelegateCommand<object> OkCommand { get; set; }
private bool enableOk = false;
private bool setOK = false;
private ObservableCollection<MyEntity> _entites = new ObservableCollection<MyEntity>();
public MainViewModel()
{
_entites.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// handle property changing
foreach (MyEntity item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) => { if (setOK) enableOk = true; };
}
}
// handle collection changing
if (setOK) enableOk = false;
};
MyEntity me1 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
MyEntity me2 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
MyEntity me3 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
_entites.Add(me1);
_entites.Add(me2);
_entites.Add(me3);
// allow collection changes now to start enabling the ok button...
setOK = true;
RunCommand = new DelegateCommand<object>(OnRunCommnad, CanRunCommand);
OkCommand = new DelegateCommand<object>(OnOkCommnad, CanOkCommand);
}
private void OnRunCommnad(object obj)
{
MyEntity me = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
// causes ok to become enabled
_entites.Add(me);
MyEntity first = _entites[0];
// causes ok to become enabled
first.Name = "Zamboni";
}
private bool CanRunCommand(object obj)
{
return true;
}
private void OnOkCommnad(object obj)
{
}
private bool CanOkCommand(object obj)
{
return enableOk;
}
}
Here is a version MyEntity (similar to the one provided by slugster):
Only the Name property fires an event in this example...
public class MyEntity : INotifyPropertyChanged
{
private string _name = string.Empty;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Information { get; set; }
public string Details { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should implement INotifyPropertyChanged. You could do it by the following way
(as you can see, this implementation is fully thread safe)
private readonly object _sync = new object();
public event PropertyChangedEventHandler PropertyChanged
{
add { lock (_sync) _propertyChanged += value; }
remove { lock (_sync) _propertyChanged -= value; }
} private PropertyChangedEventHandler _propertyChanged;
protected void OnPropertyChanged(Expression<Func<object>> propertyExpression)
{
OnPropertyChanged(GetPropertyName(propertyExpression));
}
protected string GetPropertyName(Expression<Func<object>> propertyExpression)
{
MemberExpression body;
if (propertyExpression.Body is UnaryExpression)
body = (MemberExpression) ((UnaryExpression) propertyExpression.Body).Operand;
else
body = (MemberExpression) propertyExpression.Body;
return body.Member.Name;
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = _propertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Following the implementation I described above, you can notify about your changes by two ways
1) The first way
public int MyProperty
{
get { return _myProperty; }
set
{
if (value != __myProperty)
{
_subVersion = value;
OnPropertyChanged(MyPropertyPropertyName);
}
}
} private int _myProperty; const string MyPropertyPropertyName = "MyProperty";
2) And the second way
public int MyProperty
{
get { return _myProperty; }
set
{
if (value != _myProperty)
{
_subVersion = value;
OnPropertyChanged(() => MyProperty);
}
}
} private int _myProperty;
Another solution could be a custom observable collection that requires items to implement INotifyPropertyChanged. The user must attach a handler to the OnItemPropertyChanged event, which will be called whenever the property of an item in the collection is changed.
public class ObservableCollectionEnhanced<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public ObservableCollectionEnhanced()
: base()
{ }
public ObservableCollectionEnhanced(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in Items)
item.PropertyChanged += OnItemPropertyChanged;
}
public ObservableCollectionEnhanced(List<T> list)
: base(list)
{
foreach (T item in Items)
item.PropertyChanged += OnItemPropertyChanged;
}
public event System.ComponentModel.PropertyChangedEventHandler ItemPropertyChanged;
public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if (null != ItemPropertyChanged)
ItemPropertyChanged(sender, e);
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
item.PropertyChanged += OnItemPropertyChanged;
}
protected override void RemoveItem(int index)
{
T item = this.Items[index];
item.PropertyChanged -= OnItemPropertyChanged;
base.RemoveItem(index);
}
protected override void SetItem(int index, T item)
{
T oldItem = Items[index];
base.SetItem(index, item);
oldItem.PropertyChanged -= OnItemPropertyChanged;
item.PropertyChanged += OnItemPropertyChanged;
}
}
Configure the handler as follows:
public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Update called on {0}", sender);
}
...
collection.ItemPropertyChanged += OnItemPropertyChanged;