I'm trying to make a "Dirty" implementation with Catel.
I have a viewmodel, with a [Model] property and a few [ViewModelToModel] mapped to it.
And I added a boolean member _canGetDirty, that when set to true allows viewmodel properties to prompt a service for saving.
So my logic is that if the model property changes, _canGetDirty is set to false, so the viewmodel properties change without getting dirty, and when the model is done changing we set _canGetDirty to true anew.
The problem is that the PropertyChanged event for the model property is called before the viewmodel properties are changed, hence _canGetDirty is always true, and my service is called for saving whenever I load a new model.
How to work around this issue?
public class MyViewModel : ViewModelBase
{
private IMyService _myService;
private bool _canGetDirty;
public MyViewModel(MyModel myModel, IMyService myService)
{
MyModel = myModel;
_myService = myService;
}
[Model]
public MyModel MyModel
{
get { return GetValue<MyModel>(MyModelProperty); }
set
{
_canGetDirty = false;
SetValue(MyModelProperty, value);
}
}
[ViewModelToModel("MyModel")]
public string Prop1
{
get { return GetValue<string>(Prop1Property); }
set { SetValue(Prop1Property, value); }
}
[ViewModelToModel("MyModel")]
public string Prop2
{
get { return GetValue<string>(Prop2Property); }
set { SetValue(Prop2Property, value); }
}
[ViewModelToModel("MyModel")]
public string Prop3Contains
{
get { return GetValue<string>(Prop3Property); }
set { SetValue(Prop3Property, value); }
}
#region Registering
public static readonly PropertyData Prop1Property = RegisterProperty("Prop1", typeof(string), null, PropertyToSaveChanged);
public static readonly PropertyData Prop2Property = RegisterProperty("Prop2", typeof(string), null, PropertyToSaveChanged);
public static readonly PropertyData Prop3Property = RegisterProperty("Prop3", typeof(string), null, PropertyToSaveChanged);
public static readonly PropertyData MyModelProperty = RegisterProperty("MyModel", typeof(MyModel), null, MyModelChanged);
#endregion
#region Property Changed Handlers
private static void MyModelChanged(object sender, PropertyChangedEventArgs e)
{
(sender as MyViewModel)._canGetDirty = true;
}
private static void PropertyToSaveChanged(object sender, PropertyChangedEventArgs e)
{
var vm = sender as MyViewModel;
if (vm._canGetDirty)
vm._myService.AskForSaving();
}
#endregion
}
Edit: some explanation on how Catel works in this context.
Registered properties changes:
We register properties that will notify updates with RegisterProperty:
public static readonly PropertyData Prop1Property = RegisterProperty("Prop1",
typeof(string), null, PropertyToSaveChanged);
The last parameter is a callback function called when the registered property changes.
Automatic update of model's properties:
We set a property as a Model:
[Model]
public MyModel MyModel
{
get { return GetValue<MyModel>(MyModelProperty); }
set
{
_canGetDirty = false;
SetValue(MyModelProperty, value);
}
}
This class contains a few properties (Prop1, Prop2, Prop3). Catel allows us to automatically update them from the viewmodel by mapping them with ViewModelToModel:
[ViewModelToModel("MyModel")]
public string Prop1
{
get { return GetValue<string>(Prop1Property); }
set { SetValue(Prop1Property, value); }
}
First of all, I recommend using Catel.Fody, that simplifies your property registration a lot (and yes, it also supports change callbacks ;-) ).
Why would the model externally change? When the model changes (which gets injected into your ctor), it should recreate a new VM and thus you can start with a new slate.
Back to this issue: did you test whether your setter is actually being called? It could be possible that Catel internally directly calls SetValue (equal to the behavior of dependency properties) instead of calling the wrapper in your vm. Catel works this way:
You update the model
The change callback calls (so you set _canBeDirty => true)
The vm notices that the model has been changed and updates the exposes / linked properties.
My suspicion is basically that you are setting _canBeDirty => true too early.
Assuming that ViewModelBase adheres to INotifyPropertyChanged subscribe to the classes' INotifyPropertyChanged event and set the dirty flag there instead of subscribing to individual change events.
By definition that should happen after any value is set.
Example as
public MyViewModel(MyModel myModel, IMyService myService)
{
...
this.PropertyChanged += (sender, args) =>
{
if (_canGetDirty)
_myService?.AskForSaving();
};
}
You could weed out any race condition logic on the mode with the args.PropertyName check.
Related
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;
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;
}
}
}
The XAML {Binding} construct is very handy because it handles all of the PropertyChanged issues automatically. It is really impressive when you hand it a path to an object, through .NET data structures, and everything is automatically updated for you.
I would like to use the same thing in C#. I would like to have a property that is derived from the value of another property. Example
class Foo
{
public Bar Bar = new Bar();
public string ItGetter
{
get
{
return Bar.Baz.It;
}
}
}
class Bar
{
public Baz Baz = new Baz();
}
class Baz
{
public string It { get { return "You got It!"; } }
}
If you call ItGetter on a Foo, you get the It value from Baz. This works fine, except that it is not invalidated--i.e., if It changed, there would be no change notifications on the ItGetter. Furthermore, if the Foo.Bar or Bar.Baz references are changed, you would also not get change noficiations.
I can add the appropriate IChangeNotify code on the properties, but my question is: How do I code the ItGetter property such that it will call its PropertyChanged event when any of the references in the path, or the It value change? I'm hoping I don't have to manually setup property changed events on all the items in the path....
Thanks for any help!
Eric
You could take a look at dependancy properties. They allow you to define properties in the WPF property system that are backed with stacks of metadata and a detailed value resolution system.
Importantly for you they allow they allow you to register for property changed events, and they allow you to make values dependant on other stuff.
There are some other good artcile around such as 'Demystifying dependency properties' by Josh Smith and 'Dependency Properties' by Christian Mosers
You might also want to read Dependency Property Callbacks and Validation
Here is the full code that does what I was looking for, using Dependency Properties, as Simon mentioned:
// This class exists to encapsulate the INotifyPropertyChanged requirements
public class ChangeNotifyBase : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public class Foo : ChangeNotifyBase
{
public Foo()
{
Bar = new Bar();
var binding = new Binding("Bar.Baz.It");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this, ItGetterProperty, binding);
}
/// <summary>
/// The ItGetter dependency property.
/// </summary>
public bool ItGetter
{
get { return (bool)GetValue(ItGetterProperty); }
set { SetValue(ItGetterProperty, value); }
}
public static readonly DependencyProperty ItGetterProperty =
DependencyProperty.Register("ItGetter", typeof(bool), typeof(Foo));
// Must do the OnPropertyChanged to notify the dependency machinery of changes.
private Bar _bar;
public Bar Bar { get { return _bar; } set { _bar = value; OnPropertyChanged("Bar"); } }
}
public class Bar : ChangeNotifyBase
{
public Bar()
{
Baz = new Baz();
}
private Baz _baz;
public Baz Baz { get { return _baz; } set { _baz = value; OnPropertyChanged("Baz"); } }
}
public class Baz : ChangeNotifyBase
{
private bool _it;
public bool It { get { return _it; } set { _it = value; OnPropertyChanged("It"); } }
}
If you now register for events on ItGetter, you will get notified if any of these things change:
Baz.It
Foo.Bar (I.e., change the reference)
Bar.Baz " "
If you set on of the object references (Foo.Bar or Bar.Baz) to null, the value of ItGetter changes to false.
I'm trying to use the MVVM pattern in my Silverlight 3 application and am having problems getting binding to a command property of a view model working. First off, I'm trying to add an attached property called ClickCommand, like this:
public static class Command
{
public static readonly DependencyProperty ClickCommandProperty =
DependencyProperty.RegisterAttached(
"ClickCommand", typeof(Command<RoutedEventHandler>),
typeof(Command), null);
public static Command<RoutedEventHandler> GetClickCommand(
DependencyObject target)
{
return target.GetValue(ClickCommandProperty)
as Command<RoutedEventHandler>;
}
public static void SetClickCommand(
DependencyObject target, Command<RoutedEventHandler> value)
{
// Breakpoints here are never reached
var btn = target as ButtonBase;
if (btn != null)
{
var oldValue = GetClickCommand(target);
btn.Click -= oldValue.Action;
target.SetValue(ClickCommandProperty, value);
btn.Click += value.Action;
}
}
}
The generic Command class is a wrapper around a delegate. I'm only wrapping a delegate because I wondered if having a delegate type for a property was the reason things weren't working for me originally. Here's that class:
public class Command<T> /* I'm not allowed to constrain T to a delegate type */
{
public Command(T action)
{
this.Action = action;
}
public T Action { get; set; }
}
Here's how I am using the attached property:
<Button u:Command.ClickCommand="{Binding DoThatThing}" Content="New"/>
The syntax seems to be accepted, and I think that when I tested all of this with a string property type, that worked fine. Here's the view model class that is being bound to:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Command<RoutedEventHandler> DoThatThing
{
get
{
return new Command<RoutedEventHandler>(
(s, e) => Debug.WriteLine("Never output!"));
}
}
}
The delegate contained in the Command property is never invoked. Also, when I place breakpoints in the getter and setter of the attached property, they are never reached.
In trying to isolate the problem, I changing the property type to string; the breakpoint in the getter and setter was also never reached, yet throwing an exception in them did cause the application to terminate, so I am thinking it's a framework eccentricity.
Why is this stuff not working? I also welcome alternate, hopefully simpler ways to bind event handlers to view models.
You have at least two problems here.
First, you are relying on the SetXxx method being executed. The CLR wrappers for dependency properties (the property setter or SetXxx method) are not executed when the DP is set from XAML; rather, WPF sets the value of the internally managed DP "slot" directly. (This also explains why your breakpoints were never hit.) Therefore, your logic for handling changes must always occur in the OnXxxChanged callback, not in the setter; WPF will call that callback for you when the property changes regardless of where that change comes from. Thus (example taken from a slightly different implementation of commands, but should give you the idea):
// Note callback in PropertyMetadata
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Click),
new PropertyMetadata(OnCommandChanged));
// GetXxx and SetXxx wrappers contain boilerplate only
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// WPF will call the following when the property is set, even when it's set in XAML
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ButtonBase button = d as ButtonBase;
if (button != null)
{
// do something with button.Click here
}
}
Second, even with this change, setting ClickCommand on a control that doesn't already have a value set will cause an exception, because oldValue is null and therefore oldValue.Action causes a NullReferenceException. You need to check for this case (you should also check for newValue == null though this is unlikely ever to happen).
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)