In my current project I've faced the following situation:
VM1 is used to be shown on a screen.
VM1 has a public property of VM2.
VM1 has a public property of VM3.
VM3 has a propertry that depends on VM2.
VM1 has no disposing mechanism.
At the beginning I thought about hooking to VM2.PropertyChanged event to check for the property I want and change the VM3 affected property accordingly, as:
public class VM1 : INotifyPropertyChanged
{
public property VM2 VM2 { get; private set; }
public property VM3 VM3 { get; private set; }
public VM1()
{
this.VM2 = new VM2();
this.VM3 = new VM3();
this.VM2.PropertyChanged += this.VM2_PropertyChanged;
}
private void VM2_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// if e.PropertyName equals bla then VM3.SomeProperty = lol.
}
}
This means that, since I can not unhook the event in this class, I have a memory leak.
So I end up passing an Action to VM2 so that it will be called when its important property changes the value, as:
public class VM2 : INotifyPropertyChanged
{
public Action WhenP1Changes { get; set; }
private bool _p1;
public bool P1
{
get
{
return _p1;
}
set
{
_p1 = value;
this.WhenP1Changes();
this.PropertyChanged(this, new PropertChangedEventArgs("P1");
}
}
}
public class VM1 : INotifyPropertyChanged
{
public VM2 VM2 { get; private set; }
public VM3 VM3 { get; private set; }
public VM1()
{
this.VM2 = new VM2();
this.VM3 = new VM3();
this.VM2.WhenP1Changes = () => VM3.SomeProperty = "lol";
}
}
Do I have a memory leak here?
PD: It would be great if you can also answer to:
- Is this even a good practice?
Thanks
Do I have a memory leak here?
The lambda assigned to VM2.WhenP1Changes captures this VM1 instance (needed to access the VM3 property), so as long as the view model VM2 is alive, it will keep VM1 alive. Whether this ends up being a leak depends on the lifecycle of those view models, but the implications are effectively the same as your first example using events.
In short, I would prefer to use a delegate to notify between view models. If you have a parent view model which has access to the various child view models (it seems that you do), then you can use one or more delegates like events to notify each other when updates are required.
Rather than typing out the whole scenario again, I'd prefer to point you towards my answer to the Passing parameters between viewmodels question, which provides a full description and code examples of this technique.
There is also a further addition that may interest you that can be found in my answer to the If necessary, how to call functions in MainViewViewModel from other ViewModels question. Please let me know if you have any questions.
Related
I've data architecture issues. My goal should be to have bidirectional data communication
between ViewModels and the Model classes. I've one window with different usercontrols. Each usercontrol
has it's own data, but some properties are shared between these.
For each ViewModel I implemented two functions for synchronize the model and the viewmodel.
The model should kept updated, so I implemented in the PropertyChanged event the method call SyncModel.
This is so far not so nice, because when I call the constructor the method call chain is:
constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
Here is some sample code to understand my problem better:
public class SampleModel
{
public string Material { get; set; }
public double Weight { get; set; }
public double Length { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public object SharedProperty { get; set; }
}
public class SampleViewModelA : AbstractViewModel
{
public string Material
{
get
{
return _Material;
}
set
{
if (value != _Material)
{
_Material = value;
OnPropertyChanged(nameof(Material));
}
}
}
public double Weight
{
get
{
return _Weight;
}
set
{
if (value != _Weight)
{
_Weight = value;
OnPropertyChanged(nameof(Weight));
}
}
}
public object SharedProperty
{
get
{
return _SharedProperty;
}
set
{
if (value != _SharedProperty)
{
_SharedProperty = value;
OnPropertyChanged(nameof(SharedProperty));
}
}
}
public SampleViewModelA(SampleModel Instance) : base(Instance) { }
public override void SyncModel()
{
//If I wouldn't check here, it would loop:
//constructor -> SyncViewModel -> Property setter -> PropertyChanged -> SyncModel
if (Instance.Material == Material &&
Instance.Weight == Weight &&
Instance.SharedProperty == SharedProperty)
return;
Instance.Material = Material;
Instance.Weight = Weight;
Instance.SharedProperty = SharedProperty;
}
public override void SyncViewModel()
{
Material = Instance.Material;
Weight = Instance.Weight;
SharedProperty = Instance.SharedProperty;
}
private string _Material;
private double _Weight;
private object _SharedProperty;
}
public class SampleViewModelB : AbstractViewModel
{
//Same like SampleViewModelA with Properties Length, Width, Height AND SharedProperty
}
public abstract class AbstractViewModel : INotifyPropertyChanged
{
//All ViewModels hold the same Instance of the Model
public SampleModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public AbstractViewModel(SampleModel Instance)
{
this.Instance = Instance;
SyncViewModel();
}
protected virtual void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
SyncModel();
}
public abstract void SyncModel();
public abstract void SyncViewModel();
}
The real problem is, that the SharedProperty need to be updated between the SampleViewModelA and the SampleViewModelB. First I thought the Observer Pattern could help me, but the SharedProperties are to various to make it work with generic interfaces. Then I thought a datacontroller with change events could help me like this
public class SampleDataController
{
public SampleModel Instance { get; set; }
public delegate void SynchronizeDelegate();
public event SynchronizeDelegate SynchronizeEvent;
public void SetSharedProperty(object NewValue)
{
if (Instance.SharedProperty != NewValue)
{
Instance.SharedProperty = NewValue;
SynchronizeEvent?.Invoke();
}
}
}
If it would do it like this my AbstractViewModel would only communicate with the controller instead of the instance. The SyncModel function would call methods like SetSharedProperty instead of directly access.
The MainViewModel code could look like this.
public class SampleMainViewModel
{
public SampleViewModelA ViewModelA { get; set; }
public SampleViewModelB ViewModelB { get; set; }
public SampleDataController Controller { get; set; }
public SampleMainViewModel()
{
ViewModelA = new SampleViewModelA(Controller);
ViewModelB = new SampleViewModelB(Controller);
Controller.SynchronizeEvent += ViewModelA.SyncViewModel;
Controller.SynchronizeEvent += ViewModelB.SyncViewModel;
}
}
This would cause the problem, that the source for the SynchronizeEvent call is also
subscribed to the event itself. This wouldn't cause a infinity loop because I check if
values are equal to the new state, but it seems very ugly to me. There must be a better
way than this.
In my project I have 8 ViewModels and multiple model classes where I need to sychronize the data with
different shared properties.
I'm thankful for any help and hope the problems are so far understandable.
You already use a SampleMainViewModel which is composed of the other view model classes SampleViewModelA and SampleViewModelB.
Now all you have to do is to move all the properties that are shared between the view models/views (like the SharedProperty, but also Material and Weight) to the composed SampleMainViewModel or to a shared class in general. This way all your controls can bind to the same data source.
Also the communication between Model --> View Model should only take place via events: the Model can notify the View Model by exposing e.g., a DataChanged event. There is no real bi-directional communication/dependency between Model and View Model. That's the main characteristic of MVVM: the uni-directional dependency of the participating components - realized by implementing events, commands and especially by utilizing data binding.
The follwoing example shows how you bind your controls to shared properties and to unshared properties (those that are attributes of the specialized view model classes).
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyA="{Binding ViewModelA.UnsharedPropertyA}" />
<UserControlB Material="{Binding Material}"
SharedProperty="{Binding SharedProperty}"
UnsharedPropertyB="{Binding ViewModelB.UnsharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
/* Properties must raise INotifyPropertyChanged.PropertyChanged */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
// Example initialization
public SampleMainViewModel(SomeModelClass someModelClass)
{
this.ViewModelA = new SampleViewModelA();
this.ViewModelB = new SampleViewModelB();
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
someModelClass.DataChanged += UpdateData_OnDataChanged;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
public object UnsharedPropertyA { get; set; }
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
public object UnsharedPropertyB { get; set; }
}
"Your suggestion cause the disadvantage, that my separated ViewModels
are no longer encapsulated. And if I would move all my code with
shared properties to the MainViewModel, the class would end up very
large"
To address your comment: if you insist in having a view model per each control including duplicating properties etc., then you must take a different approach.
Also moving the shared/duplicate code out of your view models does not break encapsualtion - assuming that those classes do not contain duplicated code only.
But note that a view model per each control is not recommended. You have a view/page - an aggregation of multiple controls - which has a defined data context. All controls in this view share the same data context - usually, as views are structured context related.
That's why the FrameworkElement.DataContext is inherited by default.
Having a view model for each control makes things overly complicated and leads to a lot of duplicated code - and not only duplicate properties like in your example. You will find yourself duplicating logic too. And talking about testability, if you duplicate logic you will duplicate unit tests too.
This is because you are dealing with the same data and the same model classes.
You usually extract duplicate code to a separate class that is the referenced by the types that depend on this duplicate code. Refactoring your view model classes with this "no duplicate code" policy in mind would end up moving the "shared" properties to a separate class. Since we are talking about the data context of the same view, this separate class would be the view model class that is assigned to the DataContext of the page. I'm trying to say that your approach is doing the exact opposite: you duplicate code (and call it encapsulation). If the class ends up being very large because it contains a lot of properties then you may review your UI design - maybe you should split your big page into more pages with more concise content. This may will improve UX too.
Generally, there is nothing wrong with having a view model with some more properties. If your view model class contains lots of logic too, you can extract this logic to separate classes.
You can still use the pattern of the previous example, which is to listen to data changed events of the Model.
Either you implement a very general event like the above DataChanged event or several more specialized events like a MaterialChanged event. Also make sure to inject the same model instances into each view model.
The following example shows how you can have multiple different view model classes that expose the same data, where all these view model classes update themselves by observing their model classes:
MainWindow.xaml
<Window>
<Window.DataContext>
<SampleMainViewModel />
</Window.DataContext>
<StackPanel>
<UserControlA DataContext="{Binding ViewModelA}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyA}" />
<UserControlB DataContext="{Binding ViewModelB}"
Material="{Binding Material}"
Weight="{Binding Weight}"
SharedProperty="{Binding SharedPropertyB}" />
</StackPanel>
</Window>
SampleMainViewModel.cs
public class SampleMainViewModel : INotifyPropertyChanged
{
public SampleViewModelA ViewModelA { get; }
public SampleViewModelB ViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.ViewModelA = new SampleViewModelA(sharedModelClass);
this.ViewModelB = new SampleViewModelB(sharedModelClass);
}
}
SampleViewModelA.cs
public class SampleViewModelA : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelA(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
SampleViewModelB.cs
public class SampleViewModelB : INotifyPropertyChanged
{
/* Shared properties */
public string Material { get; set; }
public double Weight { get; set; }
public object SharedProperty { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public SampleViewModelB(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
this.SharedProperty = this.SomeModelClass.SharedProperty;
// Listen to model changes
this.SomeModelClass.DataChanged += UpdateData_OnDataChanged;
this.SomeModelClass.MaterialChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void OnModelMaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
}
private void UpdateData_OnDataChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Weight = someModelClass.Weight;
this.SharedProperty = someModelClass.SharedProperty;
}
}
A variation of the first solution (which aimes to eliminate duplicate code) is to refactor your binding source, that exposes the shared properties, by extracting those properties to new classes according to their responsibilities.
For example, you can have your MainViewModel expose a MaterialViewModel class that encapsulates material related properties and logic. This way MaterialViewModel can be mnade available globally.
Given that you follow the one-data-context-class per view principle, you can limit the scope of the shared properties to specific pages by having only their specific view model classes expose the same MaterialViewModel instance:
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<StackPanel>
<MaterialControl DataContext="{Binding MaterialViewModel}"
Material="{Binding Material}"
Weight="{Binding Weight}" />
<UserControlB ... />
</StackPanel>
</Window>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
// Since defined in 'MainViewModel' the properties of 'MaterialViewModel'
// are globally shared accross pages
public MaterialViewModel MaterialViewModel { get; }
/* View model classes per page */
public ViewModelPageA PageViewModelA { get; }
// If 'ViewModelPageB' would expose a 'MaterialViewModel',
// you can limit the visibility of 'MaterialViewModel' to the 'ViewModelPageB' DataContext exclusively
public ViewModelPageB PageViewModelB { get; }
// Example initialization
public SampleMainViewModel()
{
var sharedModelClass = new SomeModelClass();
this.MaterialViewModel = new MaterialViewModel(sharedModelClass);
this.ViewModelPageA = new ViewModelPageA(sharedModelClass);
// Introduce the MaterialViewModel to a page specific class
// to make the properties of 'MaterialViewModel' to be shared inside the page only
this.ViewModelPageB = new ViewModelPageB(sharedModelClass);
}
}
MaterialViewModel.cs
public class MaterialViewModel : INotifyPropertyChanged
{
public string Material { get; set; }
public double Weight { get; set; }
private SomeModelClass SomeModelClass { get; }
// Example initialization
public MaterialViewModel(SomeModelClass sharedModelClass)
{
this.SomeModelClass = sharedModelClass;
this.Material = this.SomeModelClass.Material;
this.Weight = this.SomeModelClass.Weight;
// Listen to model changes
this.SomeModelClass.MaterialDataChanged += OnModelMaterialChanged;
}
// Example command handler to send dat back to the model.
// This will also trigger the model to raise corresponding data chnaged events
// to notify listening view model classes that new data is available.
// It can make more sense to define such a command in the owning class,
// like SampleMainViewModel in this case.
private void ExecuteSaveDataCommand()
=> this.SomeModelClass.SaveData(this.Material, this.Weight);
private void UpdateData_MaterialChanged(object sender, EventArgs args)
{
var someModelClass = sender as SomeModelClass;
this.Material = someModelClass.Material;
this.Weight = someModelClass.Weight;
}
}
Often in my applications built with Caliburn Micro I have a need to store some global data; this could be app specific config, authentication properties, etc. I generally put them in a class called "Session" and inject that via constructor injection so that every view model has a reference to a single instance of Session.
I found a case where I wanted a guard method on two different view models to be linked to a Session variable; the issue is guard methods are generally notified of changes in the setter of the changed variable. Since it's a global, it doesn't know what depends on it. (It occurs to me that this pattern of variables being aware of what guard is hooked into them is bad, but when it's all in the same ViewModel it doesn't matter much.)
I could throw an event, but that's messy and a lot of work for something that should be simple.
I could try to identify every spot where it may have been updated and manually notify, but that's error prone.
public class MyViewModel: Screen{
public MyViewModel(SessionInfo session){
Session = session;
}
public CanTakeAction { get { return !string.isNullOrWhitespace(Session.SomeProperty); } }
}
public class SessionInfo {
public SessionInfo(){}
public string SomeProperty { get; set; }
// this is where I would normally notify a guard method, but this is not going to work
NotifyOfPropertyChange(() => CanTakeAction); // except it doesn't know about CanTakeAction
}
One possible solution would be to introduce a base ViewModel, which has the guard methods (virtual). For Example,
public class ViewModelBase:Screen
{
private SessionInfo _sessionInfo;
public ViewModelBase(SessionInfo sessionInfo)
{
_sessionInfo = sessionInfo;
}
public void NotifyGuardMethods()
{
NotifyOfPropertyChange(nameof(CanTakeAction));
}
public virtual bool CanTakeAction { get; set; } = false;
}
For all the ViewModels that needs to be notified by the change in Session, you could now derieve from the ViewModelBase.
public class ShellViewModel:ViewModelBase
{
public override bool CanTakeAction { get=>//its own logic; set=>//its own logic; };
}
You could now introduce Events to the ViewModelBase, which could use the NotifyGuardMethods defined in the base class to notify all other view models. This ensures the messsy Events part would be restricted to one class alone (base view model).
What is the proper usage of Activation/Deactivation in conjunction with ObservableAsPropertyHelper? Given a view and viewmodel that reflects long lived (hot) observables, the subscription would need to be disposed when the view and viewmodel is unloaded. However ObservableAsPropertyHelper, which is recommended to be readonly is assigned in the constructor of the viewmodel, and cannot be part of the activation/deactivation lifecycle. What is the right way to handle these kind of situations?
public interface ILongLivedObject
{
IObservable<bool> Status { get; }
}
public class TestViewModel : ReactiveObject
{
private readonly ObservableAsPropertyHelper<bool> _status;
public bool Status => _status.Value;
public TestViewModel(ILongLivedObject obj)
{
_status = obj.Status.ToProperty(this, vm => vm.Status); //how is the subscription disposed?
}
}
This also gets me into a corner when trying to add commands that depends on this status. In my application, a common use case is to have some hardware that is on some specific status (e.g. IsOpen) and allow commands when it is true.
Without knowing better, this is what I am trying to do:
public class TestViewModel : ReactiveObject
{
private readonly ObservableAsPropertyHelper<bool> _status;
public bool Status => _status.Value;
public ReactiveCommand<Unit, Unit> DoStuff {get;}
public TestViewModel(ILongLivedObject obj)
{
_status = obj.Status.ToProperty(this, vm => vm.Status); //how is the subscription disposed?
DoStuff = ReactiveCommand.CreateFromTask(....., this.WhenAnyValue(this, x => x.Status);
}
}
If I try to move the _status creation into this.WhenActivated, the app will crash as the command is trying to get the value of status before it is created. Am I supposed to (re)create the comand during activation? This seems wrong and pretty costly?
So far, it seems better to have a regular Status property with a protected setter and make a regular subscription in this.WhenActivated - but this is what the handbook tells to avoid for "readonly" properties.
So one thing to be aware of in Reactive programming, disposing often means "unsubscribe".
You often don't need to unsubscribe since the garbage collector will take care of it for you, providing you create ObservableAsPropertyHelper (abbreviated as OAPH) only with observables generated from the current ViewModel.
In your case however, your observable/object, is related to a object outside the current ViewModel. The OAPH itself is a Disposable object.
So you can use ISupportsActivation (shortly going to have a replacement of IActivableViewModel) and pass your OAPH into it's Disposable property.
public class TestViewModel : ReactiveObject, ISupportsActivation
{
private readonly ObservableAsPropertyHelper<bool> _status;
public bool Status => _status.Value;
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public TestViewModel(ILongLivedObject obj)
{
_status = obj.Status.ToProperty(this, vm => vm.Status);
this.WhenActivated(disposables =>
{
disposables(_status);
}
}
}
The disposables parameter passed into the WhenActivated lambda is a Func that takes a IDisposable
In the view, make sure you derive off IActivatable (soon to be renamed IActivatableView) and use WhenActivated in the constructor of the view as well.
I have a view that has a group of images I get from a web service
I receive them in a list of this class:
public class ImageModel
{
public int Id { get; set; }
public string Name { get; set; }
public string imageUrl { get; set; }
}
under each image I show an up-vote button, so I added another bool property to the model above:
public bool UpVoted { get; set; }
the ListView that shows these images is bound to an ObservableCollection<ImageModel > , I want to change the voting icon through a converter that convert the value of UpVoted to the corresponding icon, when the user click the voting icon: a command execute this method:
private void OnVoting(ImageModel image)
{
Images.Single(x => x.id == image.id).UpVoted = !image.UpVoted;
}
the problem is that the UI is not updated, and to make sure that I understood the problem I turned the model to a View model and made the required changes to the UpVoted property (I'm using MVVM light library)
bool upVoted;
public bool UpVoted
{
get { return upVoted; }
set
{
Set(ref upVoted, value);
}
}
and it works now,
so I need to bind the UpVoted to the UI, so it's updated whenever it changed
first
your model class must inherit from MvxNotifyPropertyChanged
public class ImageModel : MvxNotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
private bool upVoted ;
public bool UpVoted
{
get { return upVoted ; }
set { upVoted = value; RaisePropertyChanged(() => UpVoted ); }
}
}
then with MvxValueConverter you ready to go
Mustafa's answer mentions a class that is specific to MvvmCross library.
Another alternative is TinyMvvm.
If you wish to write your own MVVM (or understand how MVVM works),
the general pattern is to implement INotifyPropertyChanged: Implement Property Change Notification, which I discuss here.
A convenient way to implement INotifyPropertyChanged, is to make a base class that does that implementation, then inherit from that base class. You can use the code in that sample as your base class. Or use a slightly different implementation, that avoids having to manually pass the property name as a string:
using System.ComponentModel;
using System.Runtime.CompilerServices;
// Use this as base class for all your "view model" classes.
// And possibly for your (domain) model classes.
// E.g.: "public class MyLoginViewModel : HasNotifyPropertyChanged".
// OR "public class MyLoginModel : HasNotifyPropertyChanged".
// Give it whatever name you want, for ViewModels I suggest "ViewModelBase".
public class HasNotifyPropertyChanged : INotifyPropertyChanged
{
// --- This is pattern to use to implement each property. ---
// This works for any property type: int, Color, etc.
// What's different from a standard c# property, is the "SetProperty" call.
// You will often write an IValueConverter (elsewhere) to use in XAML to convert from string to your property type,
// or from your property type to a type needed in your UI.
// Comment out this example property if you don't need it.
/// <summary>
/// Set to "true" at end of your initialization.
/// Then can use Property Trigger on Ready value=true in XAML to do something when your instance is ready for use.
/// For example, load something from web, then trigger to update UI.
/// </summary>
private bool _ready;
public bool Ready
{
get => _ready;
set => SetProperty(ref _ready, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property == null || !property.Equals(value))
{
property = value;
RaisePropertyChanged(propertyName);
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Again, an alternative to the above code is to use an existing MVVM library.
For another alternative, that doesn't require writing "SetProperty(..)" or "OnPropertyChanged(..)" in all of your property setters, google for info about using Fody/PropertyChanged. Then you wouldn't need any of the above code; your class would simply inherit from INotifyPropertyChanged. (And in app startup, you call a method that "injects" the needed logic into all properties of all INotifyPropertyChanged classes.)
Acknowledgement: The code pattern in example above is based on one of the open source libraries. It might be from TinyMvvm.
you do not say which sort of container that you are using but not all controls are set to support two way notification by default. so you may have to add a
Mode=TwoWay
to get notifications from the back end that data has changed. Or as the previous answer by Mustafa indicated you may need to verify that your class is implementing the InotifyPropertyChanged event with mvvm light.
I'm using Caliburn and the MVVM pattern in a WPF application, and am trying to use as many conventions as possible. One issue I'm running into is when I have to wire-up some property-changed notifications on an imported class.
Say I have a ViewModel like so:
class ViewModelA
{
private readonly IViewModelB _b;
public ViewModelA(IViewModelB b)
{
_b = b;
}
public bool CanGo
{
get { return _b.MyBoolProperty; }
}
public void Go()
{
//Does something here
}
}
What is the recommended (correct) way to call NotifyOfPropertyChange(() => CanGo) when the PropertyChanged event for MyBoolProperty is fired off ViewModelB?
In the past I've used a PropertyObserver type class to manage this.
Or am I designing this scenario completely wrong?
If your "sub"-model is exposed with a public property, you could use DependenciesAttribute to track changes:
class ViewModelA
{
public IViewModelB B {get; private set;}
public ViewModelA(IViewModelB b)
{
B = b;
}
public bool CanGo
{
get { return B.MyBoolProperty; }
}
[Dependencies("B.MyBoolProperty")]
public void Go()
{
//Does something here
}
}
To work properly, the whole property path should be composed of notifying objects.
You can also put a final "*"
[Dependencies("B.*")]
to indicate that all properties of B should cause the precondition re-evaluation; note that "*" only acts on the end of the proprerty path and just for one level of depth (it doesn't track changes on sub-models of B).