I have three ViewModels:
- MainViewModel,
- NavigatorViewModel,
- ProjectViewModel.
In the MainViewModel I have a property called CurrentProject from type ProjectViewModel:
public ProjectViewModel CurrentProject
{
get
{
return _currentProject;
}
set
{
if (_currentProject == value)
{
return;
}
_currentProject = value;
RaisePropertyChanged("CurrentProject");
}
}
In the NavigatorViewModel I have also a property CurrentProject
public ProjectViewModel CurrentProject { get { return ViewModelLocator.DesktopStatic.CurrentProject; } }
I use MVVM light. The View NavigatorView doesnt get notified if the property CurrentProject in the MainViewModel is changed.
How can I let the NavigatorView know, that the property has changed?
As a design concern, I would recommend not using a static Singleton pattern for this. You could use the Messenger class to send messages.
However, to address your current problem, you need to respond to the PropertyChanged event on the Singleton for that property:
public class NavigatorViewModel : INotifyPropertyChanged
{
public NavigatorViewModel()
{
// Respond to the Singlton PropertyChanged events
ViewModelLocator.DesktopStatic.PropertyChanged += OnDesktopStaticPropertyChanged;
}
private void OnDesktopStaticPropertyChanged(object sender, PropertyChangedEventArgs args)
{
// Check which property changed
if (args.PropertyName == "CurrentProject")
{
// Assuming NavigatorViewModel also has this method
RaisePropertyChanged("CurrentProject");
}
}
}
This solution listens for changes to the Singleton property and propagates the change to listeners of NavigatorViewModel.
Warning: Somewhere in the NavigatorViewModel you need to unhook the event or you risk a memory leak.
ViewModelLocator.DesktopStatic.PropertyChanged -= OnDesktopStaticPropertyChanged;
Related
I have a class, "BaseClass" that implements INotifyPropertyChanged and has the following:
BaseClass:
private bool isOn;
public bool IsOn
{
get { return isOn; }
set
{
isOn = value;
RaisePropertyChanged("BaseClass:IsOn");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I then have a class, "DIClass" that also implements INotifyPropertyChanged. It also has an ObservableCollection<BaseClass>:
DIClass:
public ObservableCollection<BaseClass> ClassesOfA;
private string iNPCTest;
public string INPCTest
{
get { return iNPCTest; }
set
{
iNPCTest = value;
RaisePropertyChanged("DIClass: INPCTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
My ViewModel holds an intance of "DIClass" and registers to it's PropertyChanged event. When I set the value of INPCTest in "DIClass", the ViewModel 'captures' the event correctly. However when I updated the IsOn property within the ObservableCollection, as below, the event is not picked up in the ViewModel.
ClassesOfA[0].IsOn = true;
Why is the INPC interface not working with the nested property? The question and answer here seems quite relevant, but I can't figure it out.
EDIT: additional explanation and code:
I can register to the PropetyChanged events of the ObservableCollection's items, as such:
ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;
ClassesOfA[1].PropertyChanged += DIClass_PropertyChanged;
However, this still does not bubble up to notify my ViewModel, that a property of my DIClass's ObservableCollection<BaseClass> has changed. I want to use INPC to bubble up event information / property updates up via MVVM layers. But I want to "wrap" them to make my classes cleaner/ less properties lying around
EDIT:
I add this "sketch" of my problem/scenario, with basic naming to make it easy:
To answer your question: This is by design.
ObservableCollection has two events:
CollectionChanged: Fires when the collection changes, e.g. collection.Add( item )
PropertyChanged: Fires when the property changes, e.g. collection = new ObservablecCollection<T>();
I think you need no ObservableCollection, because - as far as I understand your question - you want to observe the changes of the properties of the items in the collection. To achieve that you need to register to each observed item's PropertyChanged like this:
public List<BaseClass> Collection {get;set;}
public void InitializeCollection( IEnumerable<BaseClass> baseClassCollection){
Collection = new List<BaseClass>();
foreach(var item in baseClassCollection){
item.PropertyChanged += MethodToCallOnPropertyChanges;
Collection.Add( item );
}
}
public void MethodToCallOnPropertyChanges(object sender, PropertyChangedEventArgs e){
//react to any property changes
doSomething();
//react to specific properties
if(e != null && e.PropertyName.Equals("isOn"))
doSomethingOtherStuff();
}
This can be very annoying and can causes some other problems.
If I would come across this, I would think about redesigning the ViewModels and the UI. I would try to have an UI which is bound to each BaseClass item. For example, if I have an ListView I would provide an ItemTemplate in which the BaseClass item is bound. Doing so would prevent the need of registering to each item's PropertyChanged.
My suggestion is that you could create a customized ObservableCollection class that raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged.
I made a simple demo and you that you could check:
public class DIClass : INotifyPropertyChanged
{
public ExObservableCollection<BaseClass> ClassesOfA
... other code...
}
public sealed class ExObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public ExObservableCollection()
{
CollectionChanged += AllObservableCollectionCollectionChanged;
}
public ExObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void AllObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
Then you could use the ExObservableCollection class in the DIClass object. When the properties inside the BaseClass changes, the UI will be updated.
Update:
Finally, I found out the unexpected behavior you mentioned based on the complex sample. The ExObservableCollection class works well and fires the property changed event correctly.
The key point is you think if the property change event in baseclass is fired then it will
trigger the property change event in DIClass as well, right? I have to say that is not correct. The property change event only fires in the current class. It won't pass to the parent class unless you handle it in the parent class. It fired only once and notify the UI when the target property is changed.
If I understand your scenario correctly, you want to change the ToggleButton's status when the same property in BaseClassobject is changed. But the ToggleButtons are bind to VMData objects so that you need to get notified when the BaseClass objects are changed in the DIClass objects. So you want the the property change event of BaseCasss triggers the property change event of the DIClass.
Handling the property changed event of BaseClass in the DIClass object is the correct way to do what you want. It's the same like handling DIClass event in the ViewModel. But you don't want it since there might be many objects.
Then the first version of your sample is the recommended way to achieve what you want by triggering the property changed event of the DIClass on your own.
I currently have a Model with a boolean property that stores a checkbox value. When this value changes(checked/unchecked) I want to show or hide a textbox.
Now my Visibility property for the textbox is in my ViewModel and not in my Model. I am not sure how to tell my Visibility property that it should show/hide because the value of the checkbox changed.
I know in all the properties I have RaisePropertyChanged and I thinking this would be something I could use but I don't know how to capture it in my ViewModel.
Or am I approaching this all wrong?
Your ViewModel should act as a gate between your Model and your View. It looks like your checkbox is bound directly to the model. It should be bound to the corresponding View Model property that would act as a conduct to the appropriate value to your model. For example (disclaimer: I haven't used MVVM Light, but it should be self explanatory for most MVVM frameworks):
public class Chobo2
{
public bool IsChecked {get;set;}
}
public class Chobo2ViewModel // Your base class and interfaces
{
private Chobo2 model;
public bool IsChecked
{
get { return model.IsChecked; }
set
{
if(model.IsChecked == value) return;
model.IsChecked = value;
RaisePropertyChanged("IsChecked");
RaisePropertyChanged("Visibility");
}
}
public System.Windows.Visibility Visibility
{
get
{
return IsChecked
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Collapsed;
}
}
}
If your model itself implements the INotifyPropertyChanged interface and changing your view model logic isn't an option (IE your view directly binds to the model's property), all you can do is listen to the change on the PropertyChanged event.
// Assume the Chobo2 class implements INPC
public class Chobo2ViewModel // Your base class and interfaces
{
private Chobo2 model;
public Chobo2ViewModel(Chobo2 model)
{
// Should check for null here
this.model = model;
this.model.PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "IsChecked")
RaisePropertyChanged("Visibility")
}
}
public System.Windows.Visibility Visibility
{
get
{
return model.IsChecked
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Collapsed;
}
}
}
I don't know if this is possible or not, but here is the code to explain the question in the title:
public class LogicClass : INotifyPropertyChanged
{
private String _myText;
public String MyText
{
get{return _myText;}
set
{
_myText = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyText"));
}
}
...
}
public partial class Window1: Window, INotifyPropertyChanged
{
private LogicClass _logic;
public String LogicText
{
get{return _logic.MyText;}
}
...
}
<ContentControl Name="contentControl1" >
<Binding ElementName="MainWindow" Path="LogicText"/>
</ContentControl>
Is there any way to make this work, without having to expose my LogicClass variable and make use of its implementation of INotifyPropertyChanged. I guess I want to know if this can bubble up, or anything other than having to have a redundant set in my UI code-behind (which is how I am doing this now)
Yes, you need to either handle the PropertyChanged event from _logic and then raise an equivalent PropertyChanged notification on LogicText, or you need to add a standard event on MyText, so you would have a MyTextChanged event, handle this and then raise the PropertyChanged for LogicText.
So if LogicClass if never bound to directly in the Xaml, you wouldn't need to implement INotifyPropertyChanged on LogicClass and you would do (something like) this:
public class LogicClass
{
private String _myText;
public event EventHandler MyTextChanged;
public String MyText
{
get{return _myText;}
set
{
_myText = value;
var handler = MyTextChanged;
if(handler != null){ MyTextChanged(this, EventArgs.Empty); }
}
}
...
}
public partial class Window1: Window, INotifyPropertyChanged
{
private LogicClass _logic;
public Window1()
{
_logic = ... initialised;
_logic.MyTextChanged += (s,e) => RaisePropertyChanged("LogicText");
}
public String LogicText
{
get{return _logic.MyText;}
}
...
}
If what you're asking is if you can essentially say "this property represents another property on another class, so you need to look and see if it changes" in some declarative fashion, then no, that's not possible. You can, however, mimic this behavior yourself. Just attach to the PropertyChanged event on your logic class and when the MyText property changes, raise the window's PropertyChanged event by calling OnPropertyChanged.
Note that this is almost certainly better suited to something that goes in your ViewModel, not something in the codebehind on the window.
In my mvvm ViewModel I have such field
public int Delta { get; private set; }
However when I update it like that:
Delta = newValue;
UI is not refreshed.
I was thinking that databinding will do that for me. For example I can declare collection as ObservableCollection and then databinding will work.
However there are no ObservableInt, how to say View that it need to be refreshed then?
Probably I should raise some event "notify property changed" or something?
You have two choices:
Implement the INotifyPropertyChanged interface on your class.
Inherit from DependencyObject and implement Delta as a DependencyProperty.
The simplest option is #1. You can implement the INotifyPropertyChanged interface on your class quite easily:
public class YourClass : INotifyPropertyChanged
{
private int _delta;
public int Delta
{
get { return _delta; }
set { _delta = value; PropertyChanged?.Invoke(nameof(Delta)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can read more about using and implementing dependency properties on MSDN.
While we're at it with improving the answer, some of the other new additions of c# 6.0 and 7.0 help make it ever more compact:
public class Prop<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{
get => _value;
set { _value = value; NotifyPropertyChanged(nameof(Value)); }
}
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This way, you aren't using any "embedded values" (i.e - the property's name) and are keeping the code refactor-safe.
And there's also no need for redundant code blocks due to c# 6.0 and 7.0's new Expression body features
Using #LBushKin's Answer, i modified it to
public class Prop<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{
get { return _value; }
set { _value = value; NotifyPropertyChanged("Value"); }
}
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and to set it up:
class MainWindow ...
// a bool with initial value of true
public static Prop<bool> optionBool { get; set; } = new Prop<bool>{ Value = true };
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// connect UI to be able to use the Prop
DataContext = this;
}
and to use it:
<Grid ...
<CheckBox Content="Da Check" ... IsChecked="{Binding optionBool.Value}"/>
There is also a Collection and 2-Properties version here:
Utils.ObservableProperties.cs (this repo contains several related classes)
Just implement INotifyPropertyChanged Interface in your class and use it to raise a PropertyChanged for your Property and then UI will update. If you are using an MVVM project template then there is a good chance you already have a helper method implemented you only need to use it.
MSDN INotifyPropertyChanged
GalaSoft MVVM Light Toolkit
The ObservableCollection raises events automatically but for your own properties you have to raise the events yourself.
A good example is here: http://www.codeproject.com/Tips/228352/Naming-Properties-in-MVVM?display=Print
I'd suggest using mvvm light: http://mvvmlight.codeplex.com, I used it in silverlight and wpf applications. Very easy to use and provides a messageing system between model, view model and view.
Adding on to https://stackoverflow.com/a/8316100/5725669, there is a new and easy way to do this without remembering to keep track of PropertyChanged?.Invoke(nameof(Delta)); in every location
public class YourClass : INotifyPropertyChanged
{
private int _delta;
public int Delta
{
get { return _delta; }
set {
_delta = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public YourClass()
{
}
// Create the OnPropertyChanged method to raise the event
// The calling member's name will be used as the parameter.
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
It makes use of CallerMemberName for skipping manual entries for property name. More details on this MSDN Doc
I have a model that updates itself in the background. Currently I'm using INotifyPropertyChanged on the Model, but I've been told thats a bad idea as the model should be UI independent.
Is there a prefered pattern for updating the ViewModel when the Model changes in the MVVM design pattern?
Two comments:
There's nothing inherently UI-specific about INotifyPropertyChanged. It's just an interface. You are free to implement it on a Model object, and there's nothing wrong with that.
If WPF binds to a property (Foo) on the ViewModel and you fire off a PropertyChanged event even on another thread, WPF will actually call the getter of that property on the GUI thread so you don't have to deal with the Dispatcher! Caveat: If you do this, make sure your accessors for the property are thread-safe.
For instance:
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public MyViewModel(MyModel mdl)
{
mdl.PropertyChanged +=
new PropertyChangedEventHandler(
mdl_PropertyChanged);
_mdl = mdl;
}
private MyModel _mdl = null;
void mdl_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
if (e.PropertyName == "Foo")
{
this.Foo = _mdl.Foo;
}
}
public int Foo
{
get
{
lock(_foo_Lock)
{
return _foo;
}
}
set
{
lock(_foo_Lock)
{
_foo = value;
}
NotifyPropertyChanged("Foo");
}
}
private readonly object _foo_Lock = new object();
private int _foo = 0;
}
EDIT: I don't actually suggest using hard coded strings for your property names. Here's a helper class you can use to get the property name during construction using reflection. Then create an AbstractViewModel base class. You can then inherit from AbstractViewModel and implement properties like this:
#region IsCheckable
public bool IsCheckable
{
get
{
lock(m_IsCheckable_Lock)
{
return m_IsCheckable;
}
}
protected set
{
bool fire = false;
lock(m_IsCheckable_Lock)
{
if (m_IsCheckable != value)
{
m_IsCheckable = value;
fire = true;
}
}
if(fire)
{
NotifyPropertyChanged(m_IsCheckableArgs);
}
}
}
private readonly object m_IsCheckable_Lock = new object();
private bool m_IsCheckable = false;
static readonly PropertyChangedEventArgs m_IsCheckableArgs =
NotifyPropertyChangedHelper.CreateArgs<MyViewModel>(o =>
o.IsCheckable);
#endregion
the model should be UI independent.
...well the model certainly should not be aware of the View, which is why you would make your model (or ViewModel) implement INotifyPropertyChanged, so the View can bind to Properties on the Model (or VM), and let the framework's change notification inform the View of changes (so your model doesn't need to)
Of course, if you are making changes to the UI based on data from a background thread, you will need to safely dispatch them to the UI thread - you can either use a standard .net threading mechanism (like BackgroundWorker) or you can use WPF's Dispatcher class.
The purpose of the ViewModel is to allow the model to be ui independent.
Just let the viewmodel listen to an event from the model
public MyViewModel(IView view, IModel model) {
model.SomeEvent += HandleSomeEvent;
....
}
If you want to send INotifyPropertyChanged you need switch to the uithread first.
(If your model lives longer than the viewmodel you should look at some weak reference event pattern to allow the GC to clean up the viewmodel)