Consider the following object, part of a WPF MVVM application:
public class MyObject : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
And its use in the following ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private List<MyObject> _myObjects;
public List<MyObject> MyObjects
{
get
{
return _myObjects;
}
set
{
_myObjects = value;
OnPropertyChanged("MyObjects");
}
}
public bool CanDoSomething
{
get
{
return MyObjects.Where(d => d.IsSelected).Count() > 0;
}
}
}
In this situation, I can track which of my objects have been selected, and selecting them will fire OnPropertyChanged and so can notify the parent view.
However, CanDoSomething will always be false because there's nowhere I can fire an OnPropertyChanged to create a notification. If I put it in MyObject, it doesn't know anything about the property and so does nothing. There's nowhere to put it in the ViewModel because there's nothing that reacts when an object in the list is selected.
I've tried substituting the List for an ObservableCollection and a custom "TrulyObservableCollection" (see Notify ObservableCollection when Item changes ) but neither work.
How can I get round this, without resorting to click events?
I feel like if I had a better idea of what your end goal was I might be able to recommend a better approach. There is some stuff going on that just feels a little out place. Like maybe 'CanDoSomething' should be part of a command object. And I am wondering if more than one MyObject be selected at a time? If not then I would approach this in an entirely differnt way.
So anyway, you want to update CanDoSomething any time the IsSelected property of one of the items in MyObjects changes. It sounds like you were using an ObservableCollection at one point and then abandoned it. That was a mistake. You need to update CanDoSomething any time one of two events occur; the first is when items are added to or removed from MyObjects and the second is when the IsSelected property of any of the objects in MyObjects changes. For the first event you need something that implements INotifyCollectionChanged, i.e. an ObservableCollection. You already have the second event covered because the objects implement INotifyPropertyChanged. So you just have to combine those two things.
In the following example I have taken your code and made some changes. To start with I changed MyObjects back to an ObservableCollection<MyObject>. It does not have a setter because I have found that there usually is not good reason to change an observable collection; just add and remove objects as necessary. Then in the viewmodel's constructor I am register for the CollectionChanged event of MyObjects. In that handler I am grabbing items that are added to the collection and hooking up their PropertyChanged event to the OnIsSelectedChanged event handler and I am unhooking the PropertyChanged event from OnIsSelectedChanged for any objects that were removed from the collection. Because items have been added or removed we have no idea what the state of IsSelected may be of the objects in MyObjects so this is a good opportunity to update CanDoSomething, and I do at the bottom of the event handler. Finally, the OnIsSelectedChanged is where the other half of the magic happens. Every object in MyObjects will have their PropertyChanged event hooked up to this event handler. Whenever the IsSelected property on any of these objects changes the event handler will update CanDoSomething.
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
public MyViewModel()
{
this._myObjects.CollectionChanged += (o, e) =>
{
if (e.NewItems != null)
{
foreach (var obj in e.NewItems.OfType<MyObject>())
{
obj.PropertyChanged += this.OnIsSelectedChanged;
}
}
if (e.OldItems != null)
{
foreach (var obj in e.OldItems.OfType<MyObject>())
{
obj.PropertyChanged -= this.OnIsSelectedChanged;
}
}
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
};
}
private readonly ObservableCollection<MyObject> _myObjects =
new ObservableCollection<MyObject>();
public ObservableCollection<MyObject> MyObjects
{
get
{
return _myObjects;
}
}
private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
}
private bool _canDoSomething;
public bool CanDoSomething
{
get { return this._canDoSomething; }
private set
{
if (_canDoSomething != value)
{
_canDoSomething = value;
OnPropertyChanged("CanDoSomething");
}
}
}
}
First create a class that defines this attached property:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
Then just bind this attached property to a delegate command in your view model: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"
You can find more detail in this blog post: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/
Let me know if it works
Related
Source code is here:
https://github.com/djangojazz/BubbleUpExample
The problem is I am wanting an ObservableCollection of a ViewModel to invoke an update when I update a property of an item in that collection. I can update the data that it is bound to just fine, but the ViewModel that holds the collection is not updating nor is the UI seeing it.
public int Amount
{
get { return _amount; }
set
{
_amount = value;
if (FakeRepo.Instance != null)
{
//The repo updates just fine, I need to somehow bubble this up to the
//collection's source that an item changed on it and do the updates there.
FakeRepo.Instance.UpdateTotals();
OnPropertyChanged("Trans");
}
OnPropertyChanged(nameof(Amount));
}
}
I basically need the member to tell the collection where ever it is called: "Hey I updated you, take notice and tell the parent you are a part of. I am just ignorant of bubble up routines or call backs to achieve this and the limited threads I found were slightly different than what I am doing. I know it could possible be done in many ways but I am having no luck.
In essence I just want to see step three in the picture below without having to click on the column first.
Provided that your underlying items adhere to INotifyPropertyChanged, you can use an observable collection that will bubble up the property changed notification such as the following.
public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
foreach (INotifyPropertyChanged item in args.NewItems)
item.PropertyChanged += item_PropertyChanged;
if (args.OldItems != null)
foreach (INotifyPropertyChanged item in args.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
private void OnItemPropertyChanged(T sender, PropertyChangedEventArgs args)
{
if (ItemPropertyChanged != null)
ItemPropertyChanged(this, new ItemPropertyChangedEventArgs<T>(sender, args.PropertyName));
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e);
}
}
You should do two things to get it to work:
first: you should refactor the RunningTotal property so it can raise the property changed event. Like so:
private int _runningTotal;
public int RunningTotal
{
get => _runningTotal;
set
{
if (value == _runningTotal)
return;
_runningTotal = value;
OnPropertyChanged(nameof(RunningTotal));
}
}
Second thing you should do is calling the UpdateTotals after you add a DummyTransaction to the Trans. An option could be to refactor the AddToTrans method in the FakeRepo
public void AddToTrans(int id, string desc, int amount)
{
Trans.Add(new DummyTransaction(id, desc, amount));
UpdateTotals();
}
Let's suppose I have an observable collection and two clients that want to:
change it,
observe it and react on state change.
Now, if Client1 changes collection state (for example: adds new item), the collection will fire 'CollectionChanged' event. Since both clients are registered for this event notifications, Client1's handling method will be executed.
In order to avoid self-callback on Client1, I unsubscribe from an event, do my action and subscribe again. This is painful - I must remember about suspending Client1's subscription every time Client1 touches the collection and it just seems like a bad smell. Is there a better way (design pattern, external library) that would help me in callbacks management?
Although in my example I mentioned ObservableCollection and CollectionChanged event, I believe my question is more generic and comes down to: "how to exclude an entity that caused event trigger from event callback".
Thanks in advance!
Problem keeps reoccuring in my solution, bumping the question in a hope someone might help out.
I ran into your problem some times ago I didn't find a proper solution except for this one.
The idea is that when you change the collection you also pass an instance of the object changing it.
Then when the Collection fires the event, it also passes the reference.
So all observers may know which instance did the change, and check for equality.
Here is a basic example of this implementation:
class Program
{
private static MyCollection Collection;
private static MyCollectionModifier Modif1;
private static MyCollectionModifier Modif2;
static void Main(string[] args)
{
Collection = new MyCollection();
Modif1 = new MyCollectionModifier("Modifier 1", Collection);
Modif2 = new MyCollectionModifier("Modifier 2", Collection);
Modif1.AddItem("Test1");
Modif2.AddItem("Test2");
Console.ReadKey();
}
}
public class MyCollectionItemAddedEventArgs:EventArgs
{
public Object ChangeSource { get; set;}
public int newIndex {get;set;}
}
public delegate void MyCollectionItemAddedEventHandler(object sender, MyCollectionItemAddedEventArgs e);
public class MyCollection
{
private List<String> _myList;
public String this[int Index]
{
get { return _myList[Index]; }
}
public event MyCollectionItemAddedEventHandler ItemAdded;
public MyCollection()
{
_myList = new List<string>();
}
protected virtual void OnMyCollectionItemAdded(MyCollectionItemAddedEventArgs e)
{
if (ItemAdded != null)
ItemAdded(this, e);
}
public void AddItem(String Item, object ChangeSource = null)
{
_myList.Add(Item);
var e = new MyCollectionItemAddedEventArgs();
e.ChangeSource = ChangeSource;
e.newIndex = _myList.Count;
OnMyCollectionItemAdded(e);
}
}
public class MyCollectionModifier
{
private MyCollection _collection;
public string Name { get; set; }
public MyCollectionModifier(string Name, MyCollection Collection)
{
this.Name = Name;
_collection = Collection;
_collection.ItemAdded += Collection_ItemAdded;
}
public void AddItem(string Item)
{
_collection.AddItem(Item, this);
}
void Collection_ItemAdded(object sender, MyCollectionItemAddedEventArgs e)
{
if (e != null)
{
if (this.Equals(e.ChangeSource))
{
Console.WriteLine("{0} : I changed the collection", Name);
}
else
{
Console.WriteLine("{0} : Somebody else changed the collection", Name);
}
}
}
}
I've encountered this problem before as well.
Best solution I could come up with is to create extension methods that take the handler of the caller and then automate the unsubscribe/subscribe around the called method, that way you don't have to remember to do it each time and it does not end up cluttering your code either
public static void Add<T>(this ObservableCollection<T> self, T itemToAdd, NotifyCollectionChangedEventHandler handler)
{
self.CollectionChanged -= handler;
self.Add(itemToAdd);
self.CollectionChanged += handler;
}
It does take some effort to create the extensions initially but at least you won't forget to resubscribe. Only real extra code is then around invoking the method
public class ObserverClass
{
public ObserverClass()
{
ObservableIntegers.CollectionChanged += ObservableIntegersOnCollectionChanged;
//Add item to collection while preventing self-handling the callback
ObservableIntegers.Add(1, ObservableIntegersOnCollectionChanged);
}
private void ObservableIntegersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
// Handle collection change
}
public ObservableCollection<int> ObservableIntegers { get; set; }
}
I have a Windows 8.1 application with a parent and child viewmodel in the following relationship
ParentViewModel
class ParentViewModel {
private double _parentAmount;
public double parentAmount
{
get { return _parentAmount; }
set
{
if (value != _parentAmount)
{
_parentAmount = value;
NotifyPropertyChanged("parentAmount");
}
}
}
private ObservableCollection<ChildViewModel> _children;
public ObservableCollection<ChildViewModel> children
{
get { return _children; }
set
{
if (value != _children)
{
_children = value;
NotifyPropertyChanged("children");
}
}
}
}
ChildViewModel
class ChildViewModel {
private double _ChildAmount;
public double ChildAmount
{
get { return _ChildAmount; }
set
{
if (value != _ChildAmount)
{
_ChildAmount = value;
NotifyPropertyChanged("ChildAmount");
}
}
}
}
In the XAML there is TextBlock that is bound to the "ParentAmount" and then there is a ListView bound to the Observable collection "Children". ListView's Itemtemplate is a datatemplate with a TextBox with a two way bind to the "ChildAmount". The user can modify the value in the child TextBox
Now my requriement is to update the ParentAmount with the sum of all its child amount on the fly when the user modifies one of the child amounts. How do I achieve this?
For illustration purpose I have simplified the code example pasted above, the ChildViewModel has more functionality than what can be seen hence I can't replace that ObservableCollection of ChildViewModel with a List of double for instance.
I would be very glad if someone can point me in the right direction. Thanks in Advance.
With a very small addition, this will do the trick.
The specific changes are adding a property change handler for each child object in the ObservableCollection.
Note that this is a crude example to set you on the right track - I haven't unhooked the event handlers, and I recalculate the parent amount on any change from the child (i.e. I don't check that it was the ChildAmount that changed, this means you end up with more action than is necessary). I also haven't put in any code to handle changes to the contents of the ObservableCollection so if new items are added to it they won't have a property change event handler attached - this is simple for you to do yourself.
Note my use of a BaseViewModel - this is just good practice, it saves you from reimplementing the INotifyPropertyChanged interface on every class that needs it.
class ParentViewModel : BaseViewModel
{
private double _parentAmount;
public double parentAmount
{
get { return _parentAmount; }
set
{
if (value != _parentAmount)
{
_parentAmount = value;
NotifyPropertyChanged("parentAmount");
}
}
}
private ObservableCollection<ChildViewModel> _children;
public ObservableCollection<ChildViewModel> children
{
get { return _children; }
set
{
if (value != _children)
{
_children = value;
foreach (var child in _children)
child.PropertyChanged += ChildOnPropertyChanged;
NotifyPropertyChanged("children");
}
}
}
private void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
parentAmount = children.Sum(p => p.ChildAmount);
}
}
class ChildViewModel : BaseViewModel
{
private double _ChildAmount;
public double ChildAmount
{
get { return _ChildAmount; }
set
{
if (value != _ChildAmount)
{
_ChildAmount = value;
NotifyPropertyChanged("ChildAmount");
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
A more elegant way is to use Reactive Extensions.
First you need to grab
Rx-Main
from Package Manager Console.
Then, create a static class to host your extension method implemented using Rx. Something like this -
public static class Extensions
{
public static IObservable<T> OnPropertyChanges<T>(this T source, string propertyName)
where T : INotifyPropertyChanged
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
handler => handler.Invoke,
h => source.PropertyChanged += h,
h => source.PropertyChanged -= h)
.Where(p => p.EventArgs.PropertyName == propertyName)
.Select(_ => source);
}
}
Lastly, you call this method in your ParentViewModel's constructor (or anywhere necessary).
// whenever the ChildAmount property of any ChildViewModel has changed, do something
Observable.Merge(children.Select(c => c.OnPropertyChanges("ChildAmount")))
.Subscribe((c) =>
{
// update your parent amount here
NotifyPropertyChanged("parentAmount");
});
In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);
In my class I've got an ObservableCollection and I'm listening to its CollectionChanged event.
Thing is when I do Fou.ButtonData = an ObservableCollection variable , CollectionChanged event is never called and CreateButtons() never happens.
How can I make this work?
Thanks
The code
class Fou
{
private ObservableCollection<string> buttonData;
public ObservableCollection<string> ButtonData
{
get { return buttonData; }
set { buttonData = value; }
}
public Fou()
{
buttonData = new ObservableCollection<string>();
buttonData.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(buttonData_CollectionChanged);
}
void buttonData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
CreateButtons();
}
}
From the code sample it looks like the event is never raised because the ObservableCollection<string> is never actually changed. It is created, it's event assigned to and never actually modified after that. Is there some other code which modifies the collection.
Additionally there is a bug in the setter of ButtonData. You are allowing another piece of code to change the ObservableCollection<string> but you are not listening to it's CollectionChanged event nor are you disconnecting from the previous one. I would make this a readonly property or change it to the following
public ObservableCollection<string> ButtonData
{
get { return buttonData; }
set
{
if (buttonData != null) {
buttonData.CollectionChanged -= buttonData_CollectionChanged;
}
buttonData = value;
if (buttonData != null) {
buttonData.CollectionChanged += buttonData_CollectionChanged;
}
}
}