Correct way to update property in ViewModel from Model - wpf

I'm fairly novice with WPF. It's my understanding that data changes in the model, and it should notify the viewmodel, and the view will bind to properties and things alike in the viewmodel. Is this correct? If so, I've been reading that the model should implement INotifyPropertyChanged, and look something like this
public class LoginModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public bool Authenticated { get; set; }
}
and in my ViewModel, I have a property "AuthResult", that should get the update from the Model property "Authenticated"
public partial class view1 : UserControl, INotifyPropertyChanged{
public bool AuthResult
{
get
{
return _authVal;
}
set
{
_authVal = value;
NotifyPropertyChanged("AuthResult");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
I know this current implementation is incorrect. I've found that I should be subscribing to the PropertyChanged notification from my model like so:
LoginModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(LoginModel_PropertyChanged);
void LoginModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "Authenticated")
{
//do something
}
}
I don't see where the "AuthResult" property should be updated. Would I do something in the If statement like AuthResult = _model.Authenticated;?
EDITED:
and in my constructor?
LoginModel _model;
public view1(LoginModel model)
{
_model = model;
InitializeComponent();
}

If the model implements the INotifyPropertyChanged interface you can bind directly to it from the view:
<Button Content="Button" IsEnabled="{Binding Authenticated}" />
Note that the LoginModel class must raise the PropertyChanged event whenever the Authenticated property is set to a new value.
You could also expose the entire model entity through the view model class:
public class ViewModel
{
public ViewModel(LoginModel model)
{
Model = model;
}
public LoginModel Model { get; }
}
...and bind to it like this:
<Button Content="Button" IsEnabled="{Binding Model.Authenticated}" />
It is still the model class that must implement the INotifyPropertyChanged interface and raise change notifications.
Another option is for the view model to wrap any property of the model class that you want to be able to bind to from the view. Then you bind to a property of the view model class that in turn wraps a property of the model class something like this:
public class ViewModel
{
private readonly LoginModel _model;
public ViewModel(LoginModel model)
{
_model = model;
}
public bool AuthResult
{
get
{
return _model.Authenticated;
}
set
{
_model.Authenticated = value;
NotifyPropertyChanged("AuthResult");
}
}
}
<Button Content="Button" IsEnabled="{Binding AuthResult}" />
The benefit of using this latter approach is that view has no dependency upon the model class. It binds to the view model class only and this is how the MVVM design pattern typically is meant to be implemented.
But if you do bind to a (wrapper) property of the view model and want the view to be updated whenever a property of the model class is set, the model has to notify the view model that it has changed one way or another, i.e. it has to raise some kind of event or similar. And this typically means implementing the INotifyPropertyChanged interface. The view model can then subscribe to the PropertyChanged event of the model and raise its own PropertyChanged event for the data bound property whenever the model is updated, e.g.:
public class ViewModel
{
private readonly LoginModel _model;
public ViewModel(LoginModel model)
{
if (model == null)
throw new ArgumentNullException("model");
_model = model;
_model.PropertyChanged += OnModelChanged;
}
private void OnModelChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Authenticated")
NotifyPropertyChanged("AuthResult");
}
public bool AuthResult
{
get
{
return _model.Authenticated;
}
set
{
_model.Authenticated = value;
NotifyPropertyChanged("AuthResult");
}
}
}

Just use Model as member in the ViewModel
public class ViewModel
{
private Model _myModel;
public Model MyModel
{
get { return _myModel; }
set
{
if (Equals(_myModel, value)) return;
_myModel = value;
NotifyPropertyChanged(nameof(MyModel));
}
}
}
Then in xaml you can bind properties of Model
<CheckBox IsChecked="{Binding MyModel.Authenticated}" />
With this approach your ViewModel will be "build" around your Model.
In case you don't want that models implement INotifyPropertyChanged than create a "Facade" class of model in use it in same way as previous example.
public class ModelFacade : INotifyPropertyChanged
{
private Model _myModel;
public bool Authenticated
{
get { return _myModel.Authenticated; }
set
{
_myModel.Authenticated = value;
NotifyPropertyChanged(nameof(Authenticated));
}
}
}
public class ViewModel
{
private ModelFacade _myModel;
public ModelFacade MyModel
{
get { return _myModel; }
set
{
if (Equals(_myModel, value)) return;
_myModel = value;
NotifyPropertyChanged(nameof(MyModel));
}
}
}

Related

Binding to dependency property in a ViewModel

I have dependency property in SliderViewModel where this view model implements DependencyObject and is set as the data context for BRSliderUserControl. How can I bind to the dependency property in the view model from AmplitudeOptionsUserControl. Is it possible to do so. My guess is I need to create an other dependency property in BRSliderUserControl and then send the update value to the view model. Is this the right way though?
SliderViewModel.cs
public Class SliderViewModel:DependencyObject
{
public AnalysisViewType AnalysisTypeValue
{
get { return (AnalysisViewType)GetValue(AnalysisTypeDependencyProperty); }
set { SetValue(AnalysisTypeDependencyProperty, value); }
}
public static readonly DependencyProperty AnalysisTypeDependencyProperty =
DependencyProperty.Register("AnalysisTypeValue", typeof(AnalysisViewType), typeof(SliderViewModel),
new PropertyMetadata(AnalysisViewType.Unassigned, OnAnalysisTypeChanged));
private static void OnAnalysisTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do something here
}
}
BRSliderUserControl.xaml.cs
public BRSliderUserControl()
{
InitializeComponent();
SliderViewModel sliderViewModel = new SliderViewModel();
this.DataContext = sliderViewModel;
}
Now how can I bind to that dependency property from another user control?
AmplitudeOptionsControl.xaml
//This does not work..
<lib:BRSliderUserControl
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="3"
AnalysisTypeValue="{Binding AmplitudeOptionsVM.AnalysisType,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
/>
You don't need dependency property in your View Models. Your ViewModel should implement INotifyPropertyChanged interface, and your properties should raise NotifyPropertyChanged event when the value changes. There are many helpers around which makes this a bit easier.
You can use Dependency property if you want, but it makes your view models dependent on WPF, although binding to Dependency properties seems to be much faster (see here: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/optimizing-performance-data-binding)
You can see a discussion here: INotifyPropertyChanged vs. DependencyProperty in ViewModel
Also, since your DataContext is of type SliderViewModel, which has a public property named AnalysisTypeValue, in your XAML you should bind like this
... AnalysisTypeValue = {Binding AnalysisTypeValue}
Move the dependency property to the code-behind of the UserControl class:
public class BRSliderUserControl
{
public AnalysisViewType AnalysisTypeValue
{
get { return (AnalysisViewType)GetValue(AnalysisTypeDependencyProperty); }
set { SetValue(AnalysisTypeDependencyProperty, value); }
}
public static readonly DependencyProperty AnalysisTypeDependencyProperty =
DependencyProperty.Register("AnalysisTypeValue", typeof(AnalysisViewType), typeof(BRSliderUserControl),
new PropertyMetadata(AnalysisViewType.Unassigned, OnAnalysisTypeChanged));
private static void OnAnalysisTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do something here
}
public BRSliderUserControl()
{
InitializeComponent();
SliderViewModel sliderViewModel = new SliderViewModel();
this.DataContext = sliderViewModel;
}
}
Add a plain CLR property to the view model:
public class SliderViewModel : INotifyPropertyChanged
{
private AnalysisViewType _analysisTypeValue;
public AnalysisViewType AnalysisTypeValue
{
get { return _analysisTypeValue; }
set { _analysisTypeValue = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Bind the dependency property in the view to the source property of the view model:
<lib:BRSliderUserControl
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="3"
AnalysisTypeValue="{Binding AnalysisTypeValue}" />

ListBox bind to ObservableCollection is not updated with collection

I have next model:
public class MyModel
{
public ObservableCollection<MyObject> MyList {get; set;}
}
public class MyObject
{
MyObservableDictionary MyDictionary {get; set;}
}
public class MyObservableDictionary : ObservableCollection<EnymValue>
{
}
public class EnymValue : INotifyPropertyChanged
{
private MyEnum key;
private string value;
public MyEnum Key
{
get
{
return this.key;
}
set
{
this.key = value;
NotifyPropertyChanged("Key");
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public LanguageValue(MyEnum key, string value)
{
this.Key = key;
this.Value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public enum MyEnum
{
}
And on View I have a ListBox:
<ListBox x:Name="MyList" SelectionMode="Single" ItemsSource="{Binding Path=MyList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyDictionary, Mode=OneWay, Converter={StaticResource myEnumToTextConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(myEnumToTextConverter converter is just selects first element from collection and return it's value, or some specified constant if collection is null or empty)
I want my Model's list box to be updated on view, when any EnymValue values are changed.
Is it possible somehow to implement this?
Currently the view is not updated when Value changed.
I've tried to inherit EnymValue from INotifyPropertyChanged, but this didn't helped. Looks like PropertyChanged == null on EnymValue.NotifyPropertyChanged when property updated.
ObservableCollection is able to notify UI about changes when collection itself is changed(elemends are added or deleted). But ObservableCollection is not aware of changes that are happening when you modify one of it's items. To solve the problem you may subscribe to CollectionChange event of observable collection, and when new item is added, subscribe to new items's PropertyChanged. When PropertyChanged event is raised, you can trigger notification on your list OnPropertyChanged(()=>MyItems); You should be careful implementing this solution and remember to unsubscribe from the event's to avoid memory leaks.
An example of what I mean you can see in this answer.
Your MyDictionary should force a refresh. Easiest way is to re-assign its old value, and implement INPC in MyObject like below :
public class MyObject: INotifyPropertyChanged
{
MyObservableDictionary _myDictionary;
public MyObservableDictionary MyDictionary {
get
{
return _myDictionary;
}
set
{
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
public MyObject()
{
MyDictionary = new MyObservableDictionary();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Sample code to change Value :
private void Button_Click(object sender, RoutedEventArgs e)
{
// vm is ViewModel instance, vm is DataContext set for Window
var old = vm.MyList[0].MyDictionary;
vm.MyList[0].MyDictionary[0].Value = "aaaa";
vm.MyList[0].MyDictionary = old;
}
I tested this, and it displays changed value as "aaaa".

MVVM Light pass parameters to child view model

I am new to MVVM and WPF.
I am using MVVM Light to make an application which contains a DataGrid within a window, which has a view model (MainViewModel) and another window for adding and editing records in the DataGrid, that also has its own view model (EditViewModel).
What I am worried about is the approach I am using to open the Add/Edit window from the MainViewModel. In the MainViewModel I have a property SelectedItem, which is bound to the SelectedItem property of the DataGrid and an IsEdit boolean property that indicates if the Add/Edit window should be launched in Add or Edit mode.
When the Add/Edit window gets opened in edit mode, in the constructor of its view model I have the following line:
MainViewModel mainViewModel = ServiceLocator.Current.GetInstance<MainViewModel>();
That obviously retrieves the current instance of the MainViewModel, which works perfectly fine, but I am not really sure it is the best way to do this.
Also if I have more than one instances of the Main window, that use the same MainViewModel instance and I open an instance of the Add/Edit window from both of them, the Add/Edit windows are going to get data from the same instance of the MainViewModel which may be a problem.
If I try to create a new instance of MainViewModel for each MainWindow I open, then I don't know how to pass the instance of the currently used MainViewModel to the EditViewModel.
I hope I made clear what I need to do. Tell me if I have missed something and I will add it:)
Thanks in advance
Hi if I havent misunderstood your problem incorrect you can do it this way:
Since i need IsRequired dependency Property in both MainView and EditView i created a class that extends Window class
public class ExtendedWindow:Window
{
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.Register("IsRequired", typeof(bool), typeof(ExtendedWindow));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
}
MainView and ViewModel
public partial class MainWindow:ExtendedWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EditView editView = new EditView();
**((EditViewModel)editView.DataContext).IsRequired = this.IsRequired;**
editView.Show();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
IsRequired = true;
}
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
EditView and ViewModel
public partial class EditView:ExtendedWindow
{
public EditView()
{
InitializeComponent();
DataContext = new EditViewModel();
}
}
public class EditViewModel : INotifyPropertyChanged
{
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This is just kind of dummy but can give you idea how you can do it. I have tried it in dummy and its working fine.

How to communicate with ViewModel from Model

I am quit new to MVVM. So please correct me if I am doing any mistake in implementing MVVM.
In my Model class there are two properties Price and IsChecked.
public int Price { get; set; }
public static int _total;
bool _isChecked;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
if (value == true)
{
_total+= this.Price;
}
else
{
_total-= this.Price;
}
}
}
In My ViewModel Class there is a property of Type List <Model > and it is bounded to datagrid in view and another property is Total, which bounded to a textBlock in View.
public int Total
{
get
{
return DocumentStoreModel._total;
}
set
{
}
}
DataGrid has a checkBox column and it is bounded with Ischecked property
<DG:DataGridCheckBoxColumn Header="Select" Binding="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></DG:DataGridCheckBoxColumn>
Now, if user checked the Checkbox from checkbox column in DataGrid then total should be reflected in View.
My ViewModel class is implementing the INotifyPropertyChanged interface.
My qus is,if my model's property is changing how i can tell it to my viewModel ?
please let me know How i can achieve this.
From what I can tell your ViewModel's Total property is pointing to your Model's Total property so any changes you make to the Model's total will essentially be changed for the ViewModel as well.
What you might have to do though is raise the PropertyChanged event for the Total property on the ViewModel when IsChecked is changed. This will tell your View to update the data for your total text block.
As you are exposing your Model within your ViewModel, then you need to implement INotifyPropertyChanged in your Model. You however have a problem in that your Total property is static and (afaik) you cant use INotifyPropertyChanged for static properties.
I would suggest you create a custom event on your model that can be subscribed to on your ViewModel. Here's an example (you might want to tidy it up a bit).
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal static event EventHandler TotalChanged;
internal static int Total { get; private set;}
private int price;
public int Price
{
get { return price; }
set
{
price = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{ isChecked = value;
if (value)
Total += Price;
else
Total -= Price;
if (TotalChanged != null)
TotalChanged(this, EventArgs.Empty);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Total
{
get { return Model.Total; }
}
public MainViewModel()
{
Model.TotalChanged += TotalChanged;
}
private void TotalChanged(object sender, EventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Total"));
}
}
You need to implement a Command (by implementing ICommand or variants of it) and expose as property on the ViewModel and bind it on the view for the event on the checkbox.

Broken binding with Prism, Silverlight and ViewFirst approach

The problem we are having is that we cannot get binding to work in our
prism silverlight application when using the view-model first
approach. The view first approach work fine. We have gone over the
official documentation and various web sites, but have still not
resolved the issue. Below is the code for both the view-model first,
and the view first approach. Are we missing something? Read about it on my blog http://silvercasts.blogspot.com
View-Model first approach:
Bootstrapper:
internal void RegisterLoginRegionAndView()
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
() => Container.Resolve<IViewModel>().View);
}
ViewModel:
public ViewModel(IView view)
{
View = view;
View.SetModel(this);
User = new User();
User.Username = "TestUser";
}
ViewModel Interface:
public interface IViewModel
{
IView View { get; set; }
}
View Interface:
public interface IView
{
void SetModel(IViewModel model);
}
View Xaml:
<TextBox x:Name="Username" TextWrapping="Wrap" Text="{Binding User.Username}" />
View Code Behind:
public void SetModel(IViewModel viewModel)
{
this.DataContext = viewModel;
}
View first approach
Bootstrapper:
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion, typeof(IView));
ViewModel:
public ViewModel()
{
User = new User();
User.Username = "TestUser";
}
View Code Behind:
public View(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
Your implementation of SetModel on your view needs to be as follows:
public void MyUserControl : UserControl, IView
{
//...
public void SetModel(IViewModel vm)
{
this.DataContext = vm;
}
}
If that's not there, it needs to be (you haven't posted your implementation of SetModel, but this would be the source of the issue in this case).
If this is not the issue, it's likely because your ViewModel does not implement INotifyPropertyChanged. I usually use a base ViewModel that does this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then all of my ViewModels derive from that:
public class MyViewModel : ViewModelBase
{
private User _user;
public User User
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("User");
}
}
}
Note: in your case the "User" object should probably also be a ViewModel and also raise OnPropertyChanged for the Username property.
Hope this helps.
The obvious difference to me is that you set the DataContext in the "view first" approach, but not in the "view model first" approach. I'm not sure if Prism sets the DataContext for you (I'd guess that you're assuming that it does) but try setting the DataContext manually to see if this is the problem. In your ViewModel constructor you call View.SetModel(this) - does that call set the DataContext?
The problem was that I was using the SetModel method before the data object was instanced. Moving it like this:
public ViewModel(IView view)
{
View = view;
User = new User();
User.Username = "TestUser";
View.SetModel(this);
}
solved the problem.

Resources