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;
}
}
}
Related
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.
I am trying to change multiple buttons visibility from another class. This is supposed to be easy, but I just don't understand it.
The xaml part is straight forward:
<button x:Name="whatever" Visibility="{Binding whateverName}"
The view-model could be something like this?
private Visibility vis;
public Visibility Vis
{
get { return vis; }
set { vis = value; }
}
But if that is the case, how do I pass my button name?
To go a bit further, a services file is trying to modify the visibility value..
Thanks in advance.
Since you're using Bindings, you don't need the button name identifier.
The connection is made in the Binding part of the XAML:
<Button x:Name="whatever" Visibility="{Binding whateverName}"/>
What is happening there is that you are saying the Visibility property of the whatever button will be bound to the whateverName property value in your view model.
So your View model needs to look like this:
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set { vis = value; }
}
To change the visibility of your button you need to change the value of whateverName in your view model.
However, if you try, you'll notice that that won't work. The reason is that in order for the change to take effect on the button, the View model must notify the view that its property has changed. This is done with the INotifyPropertyChanged interface.
So your view model will need to look something like this:
public class Viewmodel : INotifyPropertyChanged
{
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set
{
vis = value;
OnPropertyChanged("whateverName");
}
}
public void OnPropertyChanged(string pName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In the PropertyChanged event you must pass the property name that you want to notify. In my example I just used a string value that matches the property name but there are various techniques to eliminate that "magic string".
Here's one SO question that has good answers.
I try to read properties from ViewModel that is binded to some controls on windows form. I made ViewModel as singleton, so I'm sure there is only one instance of it. The problem is, when I go to other class and get singleton instance of ViewModel to read it's properties, they are not equal with these in ViewModel class. Here is ViewModel class:
class EditorToolboxViewModel : INotifyPropertyChanged
{
static EditorToolboxViewModel instance;
public static EditorToolboxViewModel GetSingleton()
{
if (instance == null)
instance = new EditorToolboxViewModel();
return instance;
}
public event PropertyChangedEventHandler PropertyChanged;
public int BrushRadius {get; set;}
public int BrushSensitivity {get; set;}
public bool TerrainUp { get; set; }
public bool TerrainDown { get; set; }
public bool AddTerrainSlot { get; set; }
public bool RemoveTerrainSlot { get; set; }
public bool FlattenTerrain { get; set; }
public float FlattenTerrainTarget { get; set; }
private EditorToolboxViewModel()
{
BrushRadius = 10;
BrushSensitivity = 1;
}
}
And this is how I bind it to controls on windows form:
this.viewModel = EditorToolboxViewModel.GetSingleton();
this.trackBarBrushRadius.DataBindings.Add(new Binding("Value", viewModel, "BrushRadius"));
this.trackBarTerrainBrushSensitivity.DataBindings.Add(new Binding("Value", viewModel, "BrushSensitivity"));
this.radioButtonIncreaseHeight.DataBindings.Add("Checked", viewModel, "TerrainUp");
this.radioButtonDecreaseHeight.DataBindings.Add("Checked", viewModel, "TerrainDown");
this.radioButtonAddTerrainSlot.DataBindings.Add(new Binding("Checked", viewModel, "AddTerrainSlot"));
this.radioButtonRemoveTerrainSlot.DataBindings.Add(new Binding("Checked", viewModel, "RemoveTerrainSlot"));
this.radioButtonFlattenTerrain.DataBindings.Add(new Binding("Checked", viewModel, "FlattenTerrain"));
this.textBoxTerrainFlattenTarget.DataBindings.Add(new Binding("Text", viewModel, "FlattenTerrainTarget"));
And now, when I click TerrainUp radio button and try to read value from viewmodel in other class, it remains false:
bool b = EditorToolboxViewModel.GetSingleton().TerrainUp;
In ViewModel class, everything is exactly as it should, but accessing it elsewhere causes data mismatch.
Any ideas?
1) Check if the objects are the same by doing a == comparison. Properties might have different values at different time because of the binding of one windows. If you confirm that the object is the same, you know that the Singleton is working and there's probably a binding changing the property values.
2) Singleton should have a private field, not an internal field.
3) Your Singleton is not thread safe so check that.
I eventually fixed my problem.
It looks like that's known bug, that radio buttons lost bindings during state change, so that's why viewmodel wasn't behaving correctly. Look here:
http://www.abhisheksur.com/2011/03/issue-with-radiobuttons-and-binding-for.html
One of possible solutions was to replace radio buttons with check boxes. But doing this, remember two things:
Set all checkbox bindings to false in viewmodel, then set true where it's supposed to be (if you want to make checkboxes behave like radio buttons).
IMPORTANT: ViewModel is being validated not when you click checkbox, but when checkbox LOSES FOCUS. So to force viewmodel validation after single click, just programmatically change focus to other control on the form, like this:
void CheckBoxes_CheckedChanged(object sender, EventArgs e)
{
CheckBox c = (CheckBox)sender;
var otherCheckBoxName = typeof(EditorToolbox).GetRuntimeFields().Where(x => x.FieldType == typeof(CheckBox) && !x.Name.Equals(c.Name)).Select(x => x.Name).FirstOrDefault();
CheckBox c2 = (CheckBox)this.Controls.Find(otherCheckBoxName, true).FirstOrDefault();
c2.Focus();
}
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 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)