Triggering RaisePropertyChanged when Sub Property is changed - MvvmCross - c#

I have a class structure setup in the below way - when the Model's Property is changed the RaisePropertyChanged event isn't called. Is there anyway around this or do I need to flatten out the Complex Property inside the ViewModel?
Class ViewModel
{
public Model model {
get { return _Service.GetModel();}
set { _Service.SetModel(); RaisePropertyChanged(() => Model);
}
}
class Model
{
public string A {get;set;}
}
Class Service
{
}

I don't think there's any easy way around it.
Either you can change model so it supports INotifyPropertyChanged - e.g.
class Model : MvxNotifyPropertyChanged
{
public string A {
get { return _a; }
set { _a = value; RaisePropertyChanged(() => A); }
}
}
I've used this first approach when I used a stackoverflow library - which had model entities like http://stacky.codeplex.com/SourceControl/latest#trunk/source/Stacky/Entities/Answer.cs
... or you can wrap your model with another INotifyPropertyChanged class:
class ModelWrapper : MvxNotifyPropertyChanged
{
private readonly Model _m;
public ModelWrapper(Model m)
{ _m = m; }
public string A {
get { return _m.A; }
set { _m.A = value; RaisePropertyChanged(() => A); }
}
}
I've used this approach when I don't have any control over the Model class - when it's been given to me.
... or as an extension of that approach, you can flatten the property down to the parent ViewModel:
class MyViewModel : MvxViewModel
{
private readonly Model _m;
public ModelWrapper(Model m)
{ _m = m; }
public string A {
get { return _m.A; }
set { _m.A = value; RaisePropertyChanged(() => A); }
}
}
I've used this approach only when there are just a couple of properties to worry about.
Overall... remember that the ViewModel is there to be a Model of the View - it's OK to copy property values to/from Model objects.

Related

Can't bind to a member of a Model from a ViewModel

MyProduct is the model that has HasError boolean property (with OnPropertyChanged ...) that can change.
MyProductDialogViewModel is:
class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
public MyProduct Product { get; set; }
public bool HasError
{
get { return Product.HasError; }
}
}
I have assigned MyProductDialogViewModel instance to BaseContentControl.DataContext to inflate a ContentControl.
This View can be inflated with different ViewModels all having HasError property using template binding.
<ContentControl x:Name="BaseContentControl" Content="{Binding}" ... >
Then I try to extract informations directly from its DataContext.
This don't work:
<Label Content="{Binding ElementName=BaseContentControl, Path=DataContext.HasError}"/>
But this works perfectly.
<Label Content="{Binding ElementName=BaseContentControl, Path=DataContext.Product.HasError}"/>
I tought it ca be a notifiy problem in the ViewModel so I have changed to this:
class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
public MyProduct Product { get; set; }
public bool HasError
{
get { return Product.HasError; }
set
{
if (Product.HasError != value)
{
Product.HasError = value;
OnPropertyChanged("HasError");
}
}
}
}
but to no avail (in fact the set method is never called so it never notifies).
I don't want to directly refer to the specific Model instance cause the View can be inflated with different ViewModels.
How can I do ?
Thanks
You have to propagate the PropertyChanged event of MyProduct, i.e. subscribe to it and invoke OnPropertyChanged(nameof(HasError)) if HasError property of MyProduct being changed:
public class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
private MyProduct _product = null;
public MyProduct Product
{
get { return _product; }
set
{
if (_product!=null)
{
_product.PropertyChanged -= Product_PropertyChanged;
}
_product = value;
if (_product != null)
{
_product.PropertyChanged += Product_PropertyChanged;
}
}
}
private void Product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(MyProduct.HasError))
{
OnPropertyChanged(nameof(HasError));
}
}
public bool HasError => Product.HasError;
}

How do I detect changes in nested properties?

In C#, I have a suffiently complex Model. I already have a WPF Client to manipulate that model. I'm using MVVM. All objects in that model support INotifyPropertyChanged and all properties that are collections support INotifyCollectionChanged.
Take this as a simplied example:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CollectionTest1
{
public class PropertyChangedSupport : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChange([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Company : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<Employee> Employees { get; } = new ObservableCollection<Employee>();
}
public class Employee : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<PresentTimespan> PresentTimespans { get; } = new ObservableCollection<PresentTimespan>();
public Boolean IsPresentAt(DateTime t)
{
foreach (PresentTimespan pt in PresentTimespans)
{
if (pt.Start.CompareTo(t) <= 0 && pt.Finish.CompareTo(t) >= 0) return true;
}
return false;
}
}
public class PresentTimespan : PropertyChangedSupport
{
private string comment;
public String Comment { get { return comment; } set { comment = value; FirePropertyChange(); } }
private DateTime start;
public DateTime Start { get { return start; } set { start = value; FirePropertyChange(); } }
private DateTime finish;
public DateTime Finish { get { return finish; } set { finish = value; FirePropertyChange(); } }
}
public class CompanyStatusView : PropertyChangedSupport
{
private DateTime currentTime;
public DateTime CurrentTime { get { return currentTime; } set { currentTime = value; FirePropertyChange(); } }
private Company currentCompany;
public Company CurrentCompany { get { return currentCompany; } set { currentCompany = value; FirePropertyChange(); } }
public ObservableCollection<Employee> PresentEmployees { get; } = new ObservableCollection<Employee>();
public CompanyStatusView()
{
UpdatePresentEmployees();
}
private void UpdatePresentEmployees()
{
PresentEmployees.Clear();
foreach (Employee e in CurrentCompany.Employees) {
if (e.IsPresentAt(currentTime)) PresentEmployees.Add(e);
}
}
}
}
I'd like to have UpdatePresentEmployees called whenever there are changes in:
Collection Company.Employees.PresentTimespans
Property Company.Employees.PresentTimespans.Start
Property Company.Employees.PresentTimespans.Finish
Collection Company.Employees
Property CurrentTime
Property CurrentCompany
So it's basically any property or collection read by UpdatePresentEmployees.
My best solution so far included registering a lot of event handlers to all the objects mentioned above. That included to have a couple of Dictionary instances to track which added objects I have to subscribe to and especially which I have to unsubscribe from.
The most difficult and annoying part was to subscribe to all the PresentTimespan objects to listen for property changes and all the PresentTimespans collections of Employee to listen for collection changes.
My guess is that there has to be a better way to do this.
After all, in JFace (Java) there is a very interesting solution that uses ObservableTracker. So there you'd only provide the code for UpdatePresentEmployees and ObservableTracker tracks which objects have been read and automatically makes you listen for changes in any of these and also correctly unsubscribes from irrelevant objects. So there are better approaches to this problem in general. What is C# offering? Can it do better than my best solution I mentioned above? Can I avoid some of the boilerplate code? Can it be done with .net provided classes or do I need some additional classes/libraries?
Thanks for your kind help and advice in advance!
You could use BindingList instead of ObservableCollection and attach to the the ListChanged Event. But keep in mind that BindingList has some disadvantages like not being very fast. For further information this could be interesting: difference between ObservableCollection and BindingList
If you dont wanna use BindingList you have to wire your items with events.
As pointed out by Nikhil Agrawal, Rx or ReactiveUI is a good framework for my purpose. So I consider that to be a solution.

share observableCollection between ViewModels

I have a ViewModel that holds an ObservableCollection. This collection is getting updated all the time. Now I want to share this ObservableCollection with another ViewModel.
VM1
public Class VM1
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScenes = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScenes
{
get { return _cameraPackagesPerScenes; }
set { _cameraPackagesPerScenes = value; RaisePropertyChanged(); }
}
public VM1
{
var SCFunctions = new SharedCollectionLayer();
SCFunctions.AddToCameraPackagesPerSceneAndPartials(CameraPackagesPerScene);
}
}
Now I want to use the same ObservableCollection in VM2. How do I do this?
I already tried to create a new class that holds the ObservableCollection I achieve to get the data in the Collection but When I try to get it from the other VM it becomes null.
public class SharedCollectionLayer
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScenes = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScenes
get { return _cameraPackagesPerScenes ; }
set { _cameraPackagesPerScenes = value; RaisePropertyChanged(); }
}
}
public ObservableCollection<CameraPackage> GetCameraPackagesPerSceneAndPartials()
{
var CameraPackagesPerScene = new ObservableCollection<CameraPackage>();
foreach (CameraPackage camerapackage in CameraPackagesPerSceneAndPartials)
{
CameraPackagesPerScene.Add(camerapackage);
}
return CameraPackagesPerScene;
}
public void AddToCameraPackages(ObservableCollection<CameraPackage> CameraPackagesPerScene)
{
foreach(CameraPackage camerapackage in CameraPackagesPerScene)
{
CameraPackagesPerSceneAndPartials.Add(camerapackage);
}
}
}
And in VM2 I'm trying to get the items in the observableCollection but it returns empty.
VM2
public Class VM2
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScene = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScene
{
get { return _cameraPackagesPerScene; }
set { _cameraPackagesPerScene = value; RaisePropertyChanged(); }
}
public VM
{
var SCFunctions = new SharedCollectionLayer();
CameraPackagesPerScene = SCFunctions.GetCameraPackagesPerSceneAndPartials();
}
}
I'm very new to WPF and MVVM, so What is the right approach of sharing ObservableCollections between ViewModels?
Based on the information, here is what is wrong
In both of your view models you are creating new objects of SharedCollectionLayer. So both of them have different instances and hence you are not getting the values from VM1 to VM2
Solution
The way to solve this problem is to have a single object shared between both of the view models. You can either follow a singleton pattern below, or a factory pattern where the factory decides what model to give to a view model
Make your SharedCollectionLayer a singleton and then reference it in both the view models.
So you would have something like this
public class SharedCollectionLayerSingleton
{
private static SharedCollectionLayerSingleton instance
public static SharedCollectionLayerSingleton Instance
{
if(instance == null)
{
instance = new SharedCollectionLayerSingleton()
}
return instance;
}
private SharedCollectionLayerSingleton()
{
// initialize here
}
}
In the factory method, you can basically have a factory create a model or return the same model based on certain conditions. Do not have view models as the conditions but state of your application as the condition.
I do not know how your collection is changing so I cannot really comment what is a better way. Regardless of which pattern you follow make sure to keep the observable collection in the same object as long as you want views of both viewmodels reflecting the change simultaneously.
Hope this helps

Update the model from the view model

Update the model from the view model
I have read some post about the MVVM but I not sure if understand the
way that the view model is updating the model
Currently I have two text boxes in the UI which is bound to the XAML view and call to the view model when the event was raised .
when should be the place in the view model when I updating the model?
This is the view model
class ViewModel:INotifyPropertyChanged
{
private String _url;
private String _TemplateType;
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
The model
internal class DefineAddinModel
{
public string TemplateType { get; set; }
public String URL { get; set; }
}
The ViewModel usually acts as a wrapper around the Model and contains a reference to the Model which is can update either in response to commands or automatically in property setters.
UPDATE:
Here's an example of having the VM act as a wrapper around the Model. This may seem useless in your example but you will find in many cases the VM's getters/setters need to do some sort of transformation on the values rather than simply passing them through.
class ViewModel:INotifyPropertyChanged
{
private DefineAddinModel model;
public string URL
{
get { return model.URL; }
set
{
if (value != model.URL)
{
model.url = value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return model.TemplateType; }
set
{
if (value != model.TemplateType)
{
model.TemplateType = value;
OnPropertyChanged("TemplateType");
}
}
}
The better way to update your Model Is by using an event, its safer, so choose weather using a button click or lost focus, or whatever you want
void button_click(object sender,eventsarg e)
{
MyObj.URL = App.Locator.MyVM.MyDefineAddinModel.URL;// App.Locator because MVVMLight is tagged
MyObj.TemplateType = App.Locator.MyVM.MyDefineAddinModel.TemplateType ;
}
but personnaly i Use the following steps :
1- In your ViewModel create a CurrentItem object of type DefineAddinModel and without OnPropertyChanged then bind it to the View(UI) DataContext of the RootElement on the View )
2- for the model I use the INotifyPropertyChanged for each propery
3- after binding the datacontext of your root element to the CurrentItem of your ViewModel then bind just URL and TemplateType properties to your Controls, so any thing changes on the textbox will update CurrentItem properties
you can also chose the type of the binding (On LostFocus, or OnPropertyChanged)
You need to bind your TextBoxes to the two properties URL and TemplateType.
Try to use Commands (in the ViewModel)instead of events (in The CodeBehind) since you are in MVVM.
For updating the model : use a button with it's Command property bound to OnSave just like this example:
private String _url;
private String _TemplateType;
private DefineAddinModel _defineAddin;
public DefineAddinModel DefineAddin
{
get {return _defineAddin;}
set
{
_defineAddin = value;
OnPropertyChanged("DefineAddin");
}
}
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
public RelayCommand OnSaved
{
get;
set;
}
public ViewModel()
{
DefineAddin = new DefineAddinModel();
OnSaved = new RelayCommand(()=>
{
DefineAddin.URL = URL ;
DefineAddin.TemplateType = TemplateType;
});
Think about using third parties like MVVMLight it helps you a lot with MVVM and the helpers around it (Commands, Messenger, ViewModelLocator ...)
I think that the correct answer here is 'it depends'.
In most general cases, the advantage of actually using a ViewModel is also to track 'transient state', i.e. the state of an 'edit in progress' operation.
In this particular case, you would not push your changes directly to the Model every time a value is updated, instead you would do this via an 'Update' ICommand implementation that will collect all the data from the ViewModel and push it down to the Model.
This approach gives you many advantages:
The user of the view can change their mind as many times as they want, and only when they are happy will the Model actually get updated with their definitive choices
It greatly reduces the load on your persistence service, since only final changes are pushed through.
It allows you to do final validation on a complete set of values, rather than transient states, and hence reduces programming complexity and overhead.
It also makes your UI far more fluid since all the examples above are pushing updates on the UI Dispatcher, and avoids you having to cater for this via Tasks or other async approaches.
The backing model is never in an inconsistent state, since I would imagine that all values on one View/ViewModel are related, and only make sense when updated together using an ACID approach.
Here's an example of how I'd do it.
public class ViewModel:INotifyPropertyChanged {
private String _url;
private String _TemplateType;
public ViewModel(){
UpdateCommand = new DelegateCommand(OnExecuteUpdate, OnCanExecuteUpdate);
}
public bool OnCanExecuteUpdate(object param){
// insert logic here to return true when one can update
// or false when data is incomplete
}
public void OnExecuteUpdate(object param){
// insert logic here to update your model using data from the view model
}
public ICommand UpdateCommand { get; set;}
public string URL{
get { return _url; }
set {
if (value != _url) {
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType {
get { return _TemplateType; }
set {
if (value != _TemplateType) {
_TemplateType= value;
OnPropertyChanged("TemplateType");
}
}
}
... etc.
}
public class DelegateCommand : ICommand {
Func<object, bool> canExecute;
Action<object> executeAction;
public DelegateCommand(Action<object> executeAction)
: this(executeAction, null) {}
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute) {
if (executeAction == null) {
throw new ArgumentNullException("executeAction");
}
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter) {
bool result = true;
Func<object, bool> canExecuteHandler = this.canExecute;
if (canExecuteHandler != null) {
result = canExecuteHandler(parameter);
}
return result;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() {
EventHandler handler = this.CanExecuteChanged;
if (handler != null) {
handler(this, new EventArgs());
}
}
public void Execute(object parameter) {
this.executeAction(parameter);
}
}

Maintaining WPF MVVM View Model & Model Relationships

this seems like a basic question but I can't figure out the best implementation. How do you manage the relationships between two view models and their corresponding models.
For instance, if you changed the Occupation property on PersonViewModel, how does that change trickle down to the Occupation property in the PersonModel.
The only way I can see it right now is publicly exposing the model in the view model, but I thought that defeated the purpose of MVVM - decoupling the model from the view.
internal class PersonViewModel : INotifyPropertyChanged
{
private readonly PersonModel person;
private OccupationViewModel occupation;
public PersonViewModel(PersonModel person)
{
this.person = person;
}
public OccupationViewModel Occupation
{
get { return this.occupation; }
set
{
if (!this.occupation.Equals(value))
{
this.occupation = value;
this.person.Occupation = this.occupation.Occupation; // Doesn't seem right
this.OnPropertyChanged(new PropertyChangedEventArgs("Occupation"));
}
}
}
}
internal class OccupationViewModel : INotifyPropertyChanged
{
public OccupationViewModel(OccupationModel occupation)
{
this.Occupation = occupation;
}
public OccupationModel Occupation { get; set; } // Is this right?
}
internal class PersonModel
{
public OccupationModel Occupation { get; set; }
}
Your view-model is decoupling the model from the view. It seems like you may be confusing the concepts of the view and the view-model.
The view-model is both the gateway and the gatekeeper between the two -- it determines what makes it from the model to the view and from the view back to the model, and in what form.
You can set the model property in the VM setter, or not. You could save up state from the view, and only propagate those changes to the model when the user clicks "save." You could persist that state elsewhere so someone can come back and work on it more before persisting it to the view.
The view-model can know the model intimately, so the view doesn't have to know it at all.
I'm not sure I follow your concern about publicly exposing the model in the view-model. You haven't done that in your sample code. You've exposed an object that is the same type as you use in the model, but this is like using int for age in both the model and view-model -- you haven't exposed the actual model object; you still have control over whether and when the value in the view-model gets set on the model.
To show possible relationships between Model and ViewModel I first simplified your example by changing the type of Occupation into string. Then PersonModel and PersonViewModel might look like this:
public class PersonModel : INotifyPropertyChanged
{
private string occupation;
public string Occupation
{
get
{
return this.occupation;
}
set
{
if (this.occupation != value)
{
this.occupation = value;
this.OnPropertyChanged("Occupation");
}
}
}
}
public class PersonViewModel: INotifyPropertyChanged
{
private PersonModel model;
public string Occupation
{
get
{
return this.model.Occupation;
}
set
{
this.model.Occupation = value;
}
}
public PersonViewModel(PersonModel model)
{
this.model = model;
this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
}
private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
}
The important difference to your version is that PersonModel and PersonViewModel both implement INotifyPropertyChanged. This is important because otherwise changing a property of PersonModel directly (ie without going through PersonViewModel) will have no effect in the View. Also notice how the PropertyChangedEvent from the Model is piped up to the View.
Now suppose Occupation is not a string but a class with properties of its own, eg:
public class OccupationModel : INotifyPropertyChanged
{
private double salary;
public double Salary
{
get
{
return this.salary;
}
set
{
if (this.salary != value)
{
this.salary = value;
this.OnPropertyChanged("Salary");
}
}
}
}
Using a ViewModel between your View and Model gives you some flexibility on how you present your data to the View. Here are two options how you could do it:
Option 1 Expose the properties of Occupation directly in the PersonViewModel. This is a simple solution because you don't need to implement another ViewModel.
public class PersonViewModel: INotifyPropertyChanged
{
private PersonModel model;
public double OccupationSalary
{
get
{
return this.model.Occupation.Salary;
}
set
{
this.model.Occupation.Salary = value;
}
}
public PersonViewModel(PersonModel model)
{
this.model = model;
this.model.Occupation.PropertyChanged += new PropertyChangedEventHandler(occupation_PropertyChanged);
}
private void occupation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged("Occupation" + e.PropertyName);
}
}
The OccupationSalary property gives direct access to the Salary property in Occupation. Notice how now the PropertyChanged event of Occupation needs to be handled, and that we have to rename the property in occupation_PropertyChanged.
Option 2 (Recommended) Expose the properties of Occupation through an OccupationViewModel. You should do this if you need to implement any business logic specific to Occupation. Given your example, this is probably what you intended to do:
public class PersonViewModel: INotifyPropertyChanged
{
private PersonModel model;
private OccupationViewModel occupationViewModel;
public OccupationViewModel OccupationViewModel
{
get
{
return this.occupationViewModel;
}
}
public PersonViewModel(PersonModel model)
{
this.model = model;
this.occupationViewModel = new OccupationViewModel(this.model.occupation);
}
}
public class OccupationViewModel : INotifyPropertyChanged
{
private OccupationModel model;
public double Salary
{
get
{
return this.model.Salary;
}
set
{
this.model.Salary = value;
}
}
public OccupationViewModel(OccupationModel model)
{
this.model = model;
this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
}
private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
}
As you can see OccupationViewModel has exactly the same structure as the simplified PersonViewModel I showed in the beginning. The important difference to your version of OccupationViewModel is that it exposes the properties of OccupationModel, not OccupationModel itself.
Looks like you should just expose a 'Person' property on your PersonViewModel instead of exposing an Occupation property. The Occupation property seems like an uncecessary layer.
The person property would look something like below and the Occupation property could be referenced by something like this 'viewModel.Person.Occupation'.
public Person Person
{
get
{
return this.person;
}
set
{
if (!this.person.Equals(value))
{
this.person = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Person"));
}
}
}

Categories