C#, WPF, MVVM and INotifyPropertyChanged - c#

I'm getting confused; I thought I understood INotifyPropertyChanged.
I have a small WPF app with a frontend MainWindow class, a viewmodel in the middle and a model at the back.
The model in my case is the Simulator class.
The SimulatorViewModel is nearly transparent and just interfaces properties between MainWindow and Simulator.
Simulator implements INotifyPropertyChanged and each property setter in Simulator calls the RaisePropertyChanged method:
private string serialNumber;
public string SerialNumber
{
get { return serialNumber; }
set
{
serialNumber = value;
RaisePropertyChanged("SerialNumber");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
In the xaml, I have a TextBox with binding like this:
Text="{Binding Path=SerialNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
and the DataContext is the SimulatorViewModel (but see my comment about changing the DataContext to the model)
The ViewModel just passes the properties through:
public string SerialNumber
{
get { return Simulator.SerialNumber; }
set { Simulator.SerialNumber = value; }
}
Programmatic updates to the property SerialNumber in Simulator are not propagating up to the UI although curiously the initial value, set in the Simulator constructor, is getting there.
If I break on the SerialNumber setter in the Simulator and follow into RaisePropertyChanged, I find that PropertyChanged is null and so the event is not propagating upwards to the GUI.
Which leads me to several questions:
What exactly should be hooking into the PropertyChanged event? I'm looking to be more specific than just "WPF". What's the link between that event and the Binding statement in the xaml?
Why is the initial property value getting up to the UI at startup, but not subsequently?
Am I right to have Simulator (the model) implementing INotifyPropertyChanged, or should it be the ViewModel doing that? If the ViewModel does it, then programmatic changes in the model don't trigger PropertyChanged, so I'm unclear on the correct pattern. I realise my ViewModel is virtually redundant, but that's due to the simplicity of the project; a more complex one would make the ViewModel concept work harder. My understanding is that the ViewModel is the place to interface my unit tests.

The problem is that you're raising PropertyChanged on your model, but your view is bound to ViewModel. So your View subscribes only to ViewModel events (not Model's) - that is why textbox is not updated - because it never receives PropertyChanged event. One of possible solutions is to listen in ViewModel for Model PropertyChanged events and raise same event on ViewModel accordingly.
Initial value is being propagated because your setters/getters in ViewModel are correct, the problem is in events.
Yes, you are correct (see my #1), you should raise PropertyChanged on your ViewModel because View is bound to it. These events will not be triggered automatically after changes in Model, so you should listen for Model.PropertyChanged in ViewModel.
Simplest dirty fix to understand the idea:
SimulatorViewModel(Simulator model)
{
// this will re-raise Model's events on ViewModel
// VM should implement INotifyPropertyChanged
// method OnPropertyChanged should raise INPC for a given property
model.PropertyChanged += (sender, args) => this.OnPropertyChanged(args.PropertyName);
}

Always make sure that the instance of the class which has been set as Data-context should implement INotifyPropertyChanged
In your case Simulator implements INotifyPropertyChanged but SimulatorViewModel is set as DataContext, in this case the UI will come to know only if there are changes in the SimulatorViewModel but not in the Simulator.
What you can do is that:
You can expose some events or delegates in the Simulator class and hook to some methods in SimulatorViewModel class. Then when ever values in Simulator class changes, invoke the event or the delegate that will get Execute on SimulatorViewModel, now you can get the updated values from the simulator and update the properties in SimulatorViewModel. Make sure that you use different properties in both the class

Related

How to update UI change when property changed in MVVM

I'm trying to accomplish a correct MVVM architecture in WPF.
I have a player, in the "Model" section there is a Boolean property that says if we are "playing" right now.
public bool IsPlaying
{
get
{
return isPlaying;
}
set
{
isPlaying = value;
OnPropertyChanged("IsPlaying");
}
}
(Notice I implemented the "INotifyPropertyChanged" interface, so the OnPropertyChanged function reports the change)
in my ViewModel, I have a ImageSource property called "ToggleButtonIcon":
public ImageSource ToggleButtonIcon
{
get
{
if (Model.IsPlaying)
return pauseIcon;
else
return playIcon;
}
}
Which I bind to a "TogglePlayButton" in the view:
<cc:IconButton x:Name="TogglePlayButton"
Style="{StaticResource playButton}"
ImageSource="{Binding Path=ToggleButtonIcon,
UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=TogglePlayCommand}"/>
(It's a custom control, but it's working, I checked)
Of course I want the button to change its image icon according to if it is playing (pause) and if it is not playing (play).
Problem is the ToggleButtonIcon does not notify when it changes, and I can't implement the INotifyValueChanged in the ViewModel section because a. I understood that's not a part of the MVVM architexture, and b. I don't know when it changes since it depends on the IsPlaying property of Model.
I thought about putting the ToggleButtonIcon property on the Model section, but that's not "Business Logic" so I don't think that's the right way.
I also thought about using a converter and bind the IconButton directly to "IsPlaying", which would probably work, but I read here: How can WPF Converters be used in an MVVM pattern? that you should not use converters at all in MVVM because you can do any convertion you want in the "ViewModel" Section.
What's the best way to accomplish this in MVVM architecture?
To me, IsPlaying should be in the ViewModel, with change notification implemented, as it represents an application state of sorts.
To solve the issue I would recommend taking the ToggleButtonIcon out of the ViewModel, and creating a DataTrigger on the IconButton control (via its Style), that binds to the IsPlaying property (on the ViewModel) and alters the ImageSource property based on that.
The model of MVVM should only hold class entities and those entities can on occasion have an INotiftPropertyChanged, but generally they do not.
What your intent though is that it is to convey a status and that should be on your viewmodel.
I would recommend that you have the status of IsPlaying be on the View Model (VM) and bind to that. Then in the command of TogglePlayCommand, it will set that property on the VM.
That way both items update propertly on a change to either. You can still new up your original object in the model and on the Setter of the VM's IsPlaying set the class instance property to its value if needed.
Take a look at my blog post Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding. Take note on how I use OnPropertyChanged to push change messages on other operations which can all for the flexibility you seek as well as having the viewmodel hold statuses and not the models.
You should put bool on class which implements interface INotifyPropertyChange:
Here an example:
public class Game : INotifyPropertyChanged
{
private bool _isPlaying;
public string IsPlaying
{
get { return _isPlaying; }
set {
_isPlaying = value;
this.NotifyPropertyChangedBool();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChangedBool()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs("IsPlaying"));
}
}

Injecting viewmodel to view with MEF - Binding from source not working - WPF

I'm injecting my viewmodel to my view using the MEF container like below:
[ImportingConstructor]
public MainView(IRepository repository, MainViewModel viewModel)
{
InitializeComponent();
mMainViewModel = viewModel;
DataContext = viewModel;
viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
Everything works fine. I have the export attribute on both the view and viewmodel.
The problem is that when a value\property is updated on the ViewModel by some method, it does not reflect on the UI. I even tried to set Mode to Two way - still not working. I have OnPropertyChanged called for all property setters, but not working. HOWEVER, in the code behind for the view though, if I attach a propertychanged event method to the viewmodel, I get the event notifications and that is the only way I'm able to update the view using their but that should not be necessary. I just don't know why my view is not responding to propertychanged notifications directly on the bound elements.
What could I be doing wrong?
Found the issue. Turned out that in my abstract ViewModelBase class, I had the functions for notifypropertychanged...But I didn't have the INotifyPropertyChanged on the class name although I implemented the method..stupid mistake. Must have missed it. Brings up another question...why must we have the interface definition on the class for it to work?...well, all is good now. works fine now.

Prevent using Dispatcher.Invoke in WPF code

I'm a web and backend programmer by nature. Normally I try to avaoid making windows programs. Now I have to make a WPF client.
I have a background task that raises an event every often time. (It is working like a poller and when the criteria are met an event is raised). Noob as I am I wrote this code that was attached to the event to update the UI.
private void IsDisconnectedEvent()
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
}
This gives an exception because I am not on the same thread. After some googling I found that I should change the code with:
private void IsDisconnectedEvent()
{
Dispatcher.Invoke(() =>
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
});
}
This works, but this is not the only event and thus makes my code horrible ugly. Are there better ways to do this?
Regarding this:
This works, but this is not the only event and thus makes my code
horrible ugly
Yes, your WPF-based code will definitely be extremely horrible unless you understand and embrace The WPF Mentality.
Basically, all interactions between your custom logic (AKA Business logic or Application Logic) and the WPF UI should manifest in the form of Declarative DataBinding as opposed to the traditional imperative approach.
This means that there should be nothing like this:
UserWindow.Visibility = Visibility.Hidden;
anywhere in your code, simply because introducing things like that makes your code dependent on the UI and thus only executable on the UI thread.
Instead, the WPF approach to that would be to declaratively DataBind the Visibility propety of the UI element (IN XAML) to a relevant bool property that you can operate from the outside, like this:
<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>
Then, you would need to create a relevant class that contains the properties the UI is expecting to bind to. This is called a ViewModel.
Notice that in order to properly support Two-Way WPF DataBinding, your ViewModels must Implement the INotifyPropertyChanged interface.
When doing so, it is also convenient to have the PropertyChanged event from that interface marshalled to the UI thread, so that you no longer have to worry about setting the ViewModel's properties by using the Dispatcher.
Therefore our first step is to have all our ViewModels inherit from a class like this:
(taken from this answer):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Once we have our Property Change Notification Dispatch to the UI Thread in place, we can proceed to create a relevant ViewModel that suits, in this case, the UserWindow and it's DataBinding expectations:
public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
Finally, you would need to set the Window's DataContext to an instance of it's corresponding ViewModel. One simple way to do that is in the Window's constructor:
public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.
DataContext = new UserViewModel(); //here we set the DataContext
}
As you can see in this example, there is literally no need to manipulate the UI element's properties in procedural code. This is good not only because it resolves the Thread Affinity issues (because now you can set the ShowUserWindow property from any thread), but also because it makes your ViewModels and logic completely decoupled from the UI and thus testable and more scalable.
This same concept applies to EVERYTHING in WPF.
One detail that I need to mention is that I'm making use of a technique of Combining MarkupExtension and IValueConverter in order to reduce the the XAML boilerplate involved in using Converters.
You can read more about that in the link and also the MSDN DataBinding page linked above.
Let me know if you need further details.

RejectChanges() with ViewModel and MVVM design pattern and UI update

I am having a problem with DomainContext.RejectChanges() and reflecting the rollback in the UI. Here is my scenario.
I have a Model (Entity) generated for use with RIA services (I'll call it Foo)
I have a ViewModel that wraps Foo and extends it (I'll call it FooViewModel)
I have a View that is using Binding to display and update data using the FooViewModel
I have an "outer" ViewModel that holds an ObservableCollection of FooViewModels
The "outer" View has a list box bound to the ObservableCollection
So essentially there is a listbox of FooViewModels on one screen...when you select an item a childwindow is displayed to edit that particular FooViewModel. The FooViewModel is serving both the listbox and the childwindow.
Editing works just fine. A change in the childwindow reflects in the listbox immediately because I am calling RaisePropertyChanged() when the viewmodel properties are updated.
However, If I perform a DomainContext.RejectChanges()...the underlying entity gets rolled back (all changes reverted as expected)...however the FooViewModel isn't aware that this change has occurred and thus the UI isn't updated. If I reselect the item in the listbox on the first screen, the childwindow is displayed with the rolled back changes (which is what I want). The listbox still isn't updated though.
When I reject changes, if I kludge a RaiseProperyChanged() for the field that I changed...the UI listbox does update.
How do I get the UI to update when the underlying entity is rejected?? And how do I do it without tracking what properties of the viewmodel were rolledback? There has to be an easy way to accomplish this that I am just missing.
Something you could try is use the PropertyChanged event on the underlying entity Foo to trigger a RaisePropertyChanged pass on the FooViewModel properties.
so making some assumptions (so this code make sense):
You have a private variables in your FooViewModel
private Foo _foo;
private DomainContext _context;
You have a method on your FooViewModel that is calling RejectChanges() on your domain context.
Like so:
public void RejectChanges()
{
_context.RejectChanges();
}
We have a method that raises the PropertyChanged event on our FooViewModel
Like so:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
Ok, now we have that established, lets have a look at exactly what happens when you call RejectChanges() on a domain context.
When you call RejectChanges() this bubbles down through the DomainContext to its EntityContainer, then to each EntitySet in that container and then to each Entity in the set.
Once there (and in the EntitySet), it reapplies the original values if there was any, removes the entity if it was added, or adds it if it was deleted. If there was changes to the values, then it applies them back to the properties.
So theoretically, all the RaisePropertyChanged(), that are generated in the entity properties, should be triggered.
NOTE: I haven't actually tested this. If this isn't the case, then none of this works :P
So we can hook into PropertyChanged event of the Foo entity, and raise the PropertyChanged event on our FooViewModel.
so our RejectChanges() method might look like this:
public void RejectChanges()
{
Func<object, PropertyChangedEventArgs> handler = (sender, e) =>
{
RaisePropertyChanged(e.PropertyName);
};
_foo.PropertyChanged += handler;
_context.RejectChanges();
_foo.PropertyChanged -= handler;
}
So we hook up an event handler to our Foo entity, which calls the FooViewModel.RaisePropertyChanged method with the property name that is changing on the Foo entity.
Then we reject changes (which triggers the property changes),
then we unhook the event handler.
Pretty long winded, but I hope this helps :)
I presume that the call to DomainContext.RejectChanges() is happening within the ViewModel as you probably bound that to some command or method called from the parent ViewModel. Since all your bindings to the data is done on the ViewModel properties you will have to raise the property change event on them when you directly manipulate the model outside of those properties. Which you probably doing already.
public void RejectChanges()
{
DomainContext.RejectChanges();
RaisePropertyChangeOnAll();
}
How you implement RaisePropertyChangeOnAll() can be done simply with a list of RaisePropertyChange("...") for each property or you could do it through reflection (if Silverlight permissions allow, not too sure about it) by adding an Attribute on each property you want to raise. Find all the properties that are tagged with it and call RaisePropertyChanged on the MemberInfo.Name value.
[Raiseable]
public string SomeValue
{
...
}
Just an idea but may not be a perfect solution.

How can I notify that a property of an item in an observable collection has changed?

Okay, I have an ItemsControl in a XAML file that binds to an ObservableCollection. The ObservableCollection is found on a view-model class (let's call this class ViewModelA), and each item in the ObservableCollection is an instance of another view-model class (let's call the class ViewModelB).
There is a property on ViewModelA, that, when changed, will indirectly change the values of properties found in many instances of the ViewModelB class. In other words, it doesn't go straight to ViewModelB and set its properties, thus causing an INotifyPropertyChange call, but rather goes down to the model, sets some property in my model, and that change in my model affects what ViewModelB should be showing the view.
How can I notify the view that something in ViewModelB has changed?
You should be able to tell the View that the collection has changed, and in turn, trigger it to rebind to the entire collection (which would update the View).
If your Model implements INotifyPropertyChanged, the other option would be to have your ViewModelB class listen for changes on it's wrapped Model, and raise property changed events as needed.
Ideally, you'd be able to do like Reed Copsey indicated... Implement INotifyPropertyChanged on the model and have ViewModelB listen for those events. Then ViewModelB will pick up the changes no matter where the update happens.
However, In some cases the model doesn't (or can't) implement INotifyPropertyChanged. In this case, you may want to consider using an Event Aggregator pattern to pass a message between ViewModelA and the ViewModelB instances.
In this case, you could publish a "model changed" message from ViewModelA. The ViewModelB instances would subscribe to this message and each of them would get notified when A published the message. Then ViewModelB could raise the approriate PropertyChanged events to tell the UI what's changed.
You can find more info on Event Aggregator in many of the frameworks, including
Prism
Caliburn
MVVM Light
To solve this I created a class called VeryObservableCollection. For each object you add, it hooks the object's NotifyPropertyChanged event to a handler that triggers a CollectionChanged event. For each object removed, it removes the handler. Very simple and will give you exactly what you want. Partial code:
public class VeryObservableCollection<T> : ObservableCollection<T>
/// <summary>
/// Override for setting item
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
protected override void SetItem(int index, T item)
{
try
{
INotifyPropertyChanged propOld = Items[index] as INotifyPropertyChanged;
if (propOld != null)
propOld.PropertyChanged -= new PropertyChangedEventHandler(Affecting_PropertyChanged);
}
catch (Exception ex)
{
Exception ex2 = ex.InnerException;
}
INotifyPropertyChanged propNew = item as INotifyPropertyChanged;
if (propNew != null)
propNew.PropertyChanged += new PropertyChangedEventHandler(Affecting_PropertyChanged);
base.SetItem(index, item);
}
If change in Models will change in some properties of ViewModelB and those properties has change notification to UI (i.e. ViewModleB is implementing INotifyPropertyChanged), the change will immediately reflect in the UI.
So, if you have a ObservableCollection of another viewmodelB, you need not to hook up events for the propertychanged of that viewmodelB. According to my understanding, whoever changes viewmodelB's properties (model class or anyone else) and if the properties has change notification, the view will update automatically.

Categories

Resources