i just started learning MVVM and i have the following model
class ResultModel : MyMVVMBase
{
#region Field
private string _name;
#endregion
#region Poperties
public string Name
{
get
{
return _name;
}
set
{
SetField(ref _name, value, "Name");
}
}
#endregion
}
MyMVVMBase implements INotifyPropertyChanged
for this model the value is set only once when the model is created and it never changes, should it still Iimplement INotifyPropertyChanged?
No, you don't strictly need to support INotifyPropertyChanged for that property (or at all, if that's the only public property), as long as you always fully initialize the models before binding.
Related
This question already has answers here:
What is the difference between a field and a property?
(33 answers)
Closed 3 years ago.
When creating an immutable type is there any value in creating a property for the read only fields?
Public readonly fields:
public class MyClass
{
public readonly string MyText;
public MyClass (string theText)
{
MyText = theText;
}
}
or
Private readonly fields with public propery:
public class MyClass
{
private readonly string myText;
public string MyText
{
get { return myText; }
}
public MyClass(string theText)
{
myText = theText;
}
}
Fields are generally meant to contain implementation details. For that reason they should not be declared in a manner that is visible to other code bases. A public, non-internal field would violate this rule. If the caller uses the fields directly, they are violating the general SOLID principle that implementations should depend on abstractions.
In addition, properties have certain advantages over fields. Properties can be included in an interface, for example, while fields cannot. Also, properties can be virtual, abstract, and overridden, while fields cannot.
As a complement to #John Wu's answer:
In C# 6 and later you can use read-only properties assignable only in the constructor:
public string MyText { get; }
This relieves you from having a backing-field solely for supporting this property.
But the story does not end here, a property is not a field but a method.
Knowing this, it allows you to achieve things a field would not be able to such as:
Notify of value changes by implementing INotifyPropertyChanged:
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class Test : INotifyPropertyChanged
{
private string _text;
public string Text
{
get => _text;
set
{
if (value == _text)
return;
_text = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Being a value you can bind to:
In WPF and UWP you can only bind to properties, not fields.
In the end:
It depends on your requirements.
I have multiple ViewModels and use ViewModelBase as an abstract class in all of them. I want to grab the current property values from one class in another. Creating an instance is not desirable, making the property static gets me what I want. However by doing this I lose out on the INotifyPropertyChange Im using with ViewModelBase.Set() since Set() is a non-static method.
Whats the alternative where I can get the property value, yet still keep the benefits of ViewModelBase in MVVM?
public class SampleViewModel : ViewModelBase
{
private static bool _sample;
public SampleViewModel()
{
}
public static bool GetValue
{
get { return _sample; }
set { Set(ref _sample, value); }
}
}
public class MyClassViewModel : ViewModelBase
{
public MyClassViewModel()
{
bool myValue = SampleViewModel.GetValue;
}
}
ParentVM creates a ChildVM, exposing it via a ChildVM property
ParentView handles the resultant PropertyChanged event, creating a ChildView, setting its DataContext to ChildVM.
See here for details.
Or use MVVM Light Toolkit's Messaging Services to pass values to view Models.. But I don't like it.
I was given this sample code when creating a new MVVM light(WPF451) project and it made me confusing.
DataItem.cs:
public class DataItem
{
public string Title { get; private set; }
public DataItem(string title)
{
Title = title;
}
}
This class declares a set of properties that is needed in the ViewModel. It's used in the Model layer DataService, which provides data to the VM in its constructor.
DataService.cs
public class DataService : IDataService
{
public void GetData(Action<DataItem, Exception> callback)
{
// Use this to connect to the actual data service
var item = new DataItem("Welcome to MVVM Light");
callback(item, null);
}
}
I thought it would be used in the VM as well to hold properties, like this:
public DataItem Data { get; set; }
but instead, the MVVM light developer decided to re-declare the properties in the VM.
MainViewModel.cs:
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get{ return _welcomeTitle; }
set{ Set(ref _welcomeTitle, value); }
}
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
WelcomeTitle = item.Title;
});
}
}
I couldn't understand why they implemented like this. Yes, it reduces redundant INotifyPropertyChanged implemented object so it uses less resource. However, if I had to implement tons of properties to the VM, I'll have to write properties on both VM and DataItem, and also when I want to add or delete properties I'll have to edit both of them.
Couldn't I just hold a DataItem property in the VM? Or am I missing something?
DataItem simply represents the Model in this case. If the Model is an entity that cannot be modified (database auto-generated POCO), this scenario would work.
Yes, you will have to have each applicable Model property in your ViewModel so that it can RaisePropertyChanged, and yes, this is more 'work', but it provides an abstraction between the two.
Some people are okay with modifying the Model to have it implement INotiftyPropertyChanged, others believe the Model shouldn't and all the work should be done in the ViewModel (which is what is being done in this case).
I am a little bit confused with usage of BindableBase class and how to apply this "new" mechanism to the classic MVVM design.
Briefly, the question is the following: How to use correctly BindableBase class when we have reference to the model in our view-model class?
Details:
Classical MVVM pattern: View <-> View-Model -> Model
As we see View-Model in this scheme knows about Model, but Model knows nothing about View and View-Model.
If we implement this approach, we will have something like this:
// Model
class Task
{...}
// View-Model
class TaskViewModel : BindableBase
{
private readonly Task _task;
public TaskViewModel(Task task)
{
_task = task;
}
...
}
Let's imaging that Task class has 'Subject' property and we should show this data. So according to MVVM I should:
Create duplication of 'Subject' property in View-Model:
// View-Model
class TaskViewModel : BindableBase
{
public String Subject
{
get{ return _task.Subject; }
set
{
_task.Subject = value;
// I can't use SetProperty(ref _task.Subject, value)
// it's contradict c# syntax
OnPropertyChanged("Subject");
}
}
}
As you see I can't use SetProperty method for such design and the only way it's calling of raw onPropertyChanged method.
It seems that SetProperty is the biggest benefit of BindableBase class and it's very strange that we can't use it in such direct and common implementation of MVVM. So I thought maybe I missed something or work incorrectly with the specified class.
Do you know how to use BindableBase for the specified design and get some code improvement?
Thanks
Currently, your ViewModel is exposing Model properties to your View. This is fine however it becomes quite ridiculous if your Model has lots of properties that need to be exposed. Can you imagine having to create properties for a Model that has 20+ properties?
Instead, you should expose the Model to the View using a property inside your ViewModel.
public MyClass Model { get; private set; }
Note: This too can implement INotifyPropertyChanged.
And the properties in your Model should implement INotifyPropertyChanged, or in your case, BindableBase.
public class MyClass : BindableBase
Your View can then bind directly to the Model property. This may seem like you're breaking the design pattern however this is not the case, your View still knows nothing about your Model, however it simply makes assumptions to what properties it's expecting, therefore your classes are still decoupled.
The only downside here is that your Model now depends on BindableBase, this isn't the end of the world but if you are in a situation where you can't modify the Model classes, then your current approach is the way to go.
Like Mike Eason said, it is fine to expose your model, one of the main goals of the ViewModel is to get the Model ready for your view. That said, I tend to only expose the model for read-only views myself.
You could inherit from BindableBase and create a method to allow you to alter the Model's properties in the same way you do fields.
public class ViewModelBase : BindableBase
{
protected bool SetProperty(
Func<bool> isValueNew,
Action setValue,
[CallerMemberName] string propertyName = null)
{
if (isValueNew())
{
return false;
}
setValue();
OnPropertyChanged(propertyName);
return true;
}
}
The isValueNew Func is for determining if the value is different or not. You could then use it like the following:
public class MyViewModel : ViewModelBase
{
private readonly MyModel myModel = new MyModel();
public string Name
{
get { return myModel.Name; }
set
{
if (SetProperty(() => myModel.Name == value, () => myModel.Name = value))
{
// Do something here since the value was changed.
}
}
}
}
That's the easiest way I can think of to achieve what you seem to be after.
I would keep your setter for the Subject property simple.
Consider loading the data from your model first and then assigning the data to your view-model properties.
class TaskViewModel : BindableBase
{
LoadData()
{
Subject = GetSubject(); // Relies on model
}
string _subject = null;
public String Subject
{
get{ return _subject; }
set
{
_subject = value;
SetProperty(ref _task.Subject, value)
}
}
}
Try it:
class TaskViewModel : BindableBase
{
private readonly Task _task;
private string _subject;
public TaskViewModel(Task task)
{
_task = task;
Subject = _task.Subject;
}
public string Subject
{
get { return _subject; }
set
{
_task.Subject = value;
SetProperty(ref _subject, value)
}
}
}
UPD
I decided to add some explanations after the first comment.
BindableBase is just a basic version of the class that implements INotifiPropertyChanged interface. It's an implementation of the SetProperty method (for the Prism framework):
public abstract class BindableBase : INotifyPropertyChanged
{
// Some other members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals((object) storage, (object) value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
}
As can be seen, the method SetProperty if just "syntactic sugar" and the OnPropertyChanged is executing inside. Therefore, it does not really matter whether you call the method SetProperty or the OnPropertyChanged.
I recently wrote two classes and an interface as a way to implement the answer to this question of mine.
The first class is the Notifier generic class:
public interface INotifier { }
public class Notifier<T> : Observable,INotifier where T:new()
{
public Notifier()
{
V = new T();
}
private T _ValueBacker;
public T V
{
get { return _ValueBacker; }
set
{
_ValueBacker = value;
OnPropertyChanged(() => V);
}
}
}
The Observable base class here is just a class that implements INotifyPropertyChanged and defines an OnPropertyChanged method.
Thanks to that class, I can now define a Silverlight/WPF ViewModel like this:
public class Person : ViewModelBase
{
Notifier<string> Name {get;set;}
Notifier<string> Surname {get;set;}
Notifier<int> Age {get;set;}
}
instead of:
public class Person : Observable
{
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
_Name=value;
OnPropertyChanger(()=>Name);
}
}
privaate string _Surname;
public string Surname
{
get
{
return _Surname;
}
set
{
_Surname=value;
OnPropertyChanger(()=>Surname);
}
}
private int _Age;
public int Age
{
get
{
return _Age;
}
set
{
_Age=value;
OnPropertyChanger(()=>Age);
}
}
}
As you can see, the new code is much more concise and much less coding-error (or typo) prone. All I have to do in my XAML is to bind to "MyPerson.V" instead of "MyPerson". However, since there aren't any ways to implement initializers for autoproperties, I had to initialize every property in the constructor. In some cases, I skipped the initializers and that led to some runtime errors. So, to take care of that, in the constructor of the ViewModelBase class, I added this loop:
public ViewModelBase()
{
foreach(var notifierProperty in this.GetType().GetProperties().Where(c=>c.PropertyType.GetInterfaces().Any(d=>d==typeof(INotifier))))
{
notifierProperty.SetValue(this, notifierProperty.PropertyType.GetConstructor(System.Type.EmptyTypes).Invoke(null), null);
}
}
What this does is, whenever you instantiate a ViewModelBase derived class, the constructor loops through the properties, and invokes the constructor for each Notifier type property.
Is this evil? Will using reflection this way come back to haunt me in the future? Are there any performance hits I should be aware of?
I think that's fine. I have some bits of information to add:
You can create types with trivial constructors by calling Activator.Create(myType), which means you don't have to fetch a constructor.
I believe at least for Silverlight, all properties initialized with your hack need to be public.
There is a library called ReactiveProperty, that defines a class ReactiveProperty<T> very similar to your Notifier<T>.
You will bind against it's Value property:
public class ReactiveProperty<T> : IObservable<T>, IDisposable, INotifyPropertyChanged, IReactiveProperty, INotifyDataErrorInfo
{
public T Value
{
get { return latestValue; }
set { anotherTrigger.OnNext(value); }
}
// ...
}
The call in the setter eventually leads to the respective call to INotifyPropertyChanged.PropertyChanged.
ReactiveProperty<T> also is an observable in the sense of reactive extensions, on which the library depends. Other than that, the author basically does what you do, but without the initialization hack in the constructor.