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.
Related
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.
Question: I have two viewmodels that share a service with a list. My question is how to setup the notification so that both viewmodels know when this list is changed. Description below and code of where i'm at.
I found this example HERE that looks right for what I'm trying to do, but I have a question regarding how to be notified in my viewmodels of a change in my service. I'll add some code I've mocked up to see if I'm on the right track. I'm using WPF/MVVM Light.
First part is a service with a interface that will have a list of data, in this example I'm using a string list. I want a property in both viewmodels to have access to this list of data, and be notified when it's changed. I think what's throwing me is the interface IOC into my viewmodels. I'm understanding more and more why this is good, but I'm still wrapping my mind around it and I'm not sure how to setup the notification when the list is changed in the service. If my service was not injected I might have setup a event or property that my viewmodel property would access get/set but injecting my service does not expose my public fields, just methods. This is new to me so it's very likely i'm not understanding this correctly or missing something.
I used a List in my service instead of a ObservableCollection based on some reading I've done warning against using the ObservableCollection here. Thanks you for any help.
public class MyService : IMyService
{
private List<string> myList = new List<string>();
public List<string> getMyList()
{
return this.myList;
}
public void setMyList(List<string> value)
{
this.myList = value;
}
public void addValue(string value)
{
this.myList.Add(value);
}
public void insertValue(int index, string value)
{
this.myList.Insert(index, value);
}
}
public class MyViewModelOne : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelOne(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
public class MyViewModelTwo : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelTwo(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
From what I understood about your problem, what you need essentially is that your INotifyPropertyChanged implementation at your service level and the list my list to be an ObservableCollection being injected from the service.
Now if there is a notification change it would be directly on the service and hence no explicit need of handling.
Your binding could look like "{Binding MyService.MyProperty}"
I got this working two different ways, I went with the first example because I think it's easier to follow in the code.
This came up because I had a control in my mainview with related code that was growing and I realized I wanted the same control/behavior in a separate view that would use the same data/control for a different purpose.
I did not want to duplicate this control/template/code in two places so I made it into a User Control. I then nest the user control in my views. The user control has it's own VM. The main view updates the service with new data, and the nested control listens on a event to know when there is new data.
Still very new to MVVM thinking so please feel free to point out in issues with either of these examples.
Example using a service with eventhandler.
public interface IMyInterface
{
event EventHandler OnSomeEvent;
void addSomeData(string value);
void getSomeData();
}
public class MyInterface: IMyInterface
{
public event EventHandler OnSomeEvent = delegate { };
public void addSomeData(string value)
{
// do stuff
OnSomeEvent();
}
public string getSomeData()
{
return "some data";
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void testEvent()
{
this.myInterface.addSomeData("test data");
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void myInterface_OnSomeEvent(object sender, System.EventArgs e)
{
// do stuff
}
}
Example using MVVM Light Messaging
public class EventDataSource
{
public string MyItemSource { get; set; }
public EventDataSource()
{
MyItemSource = string.Empty;
}
}
// Message class
public class MyDataSourceMessage : MessageBase
{
public EventDataSource MyItemSource { get; set; }
public MyDataSourceMessage(EventDataSource myItemSource)
{
MyItemSource = myItemSource;
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
public NotifyViewModel() {}
void testMessage()
{
EventDataSource msg = new EventDataSource() { MyItemSource = "magic message!"};
Messenger.Default.Send(new MyDataSourceMessage(msg as EventDataSource));
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
public NotifyViewModel()
{
Messenger.Default.Register<MyDataSourceMessage>(this, (action) => ReceiveMessage(action));
}
private ObservableCollection<string> myProperty = new ObservableCollection<string>();
public ObservableCollection<string> MyProperty
{
get { return myProperty; }
set
{
myProperty: = value;
RaisePropertyChanged(() => MyProperty);
}
}
void ReceiveMessage(MyDataSourceMessage action)
{
// do something with the data
MyProperty.Add(action.DGItemSource.ItemSource);
}
}
Ok let me try to shed some light on this. First of all, change notification is not meant to pass information between view models, it is meant to notify the view itself that the a property of the view model has changed.
There are a few methods for view models to issue change notifications to views:
INotifyPropertyChanged interface
INotifyCollectionChanged interface
A custom event with the name of the property suffixed with Changed (e.g. an event called MyPropChanged for a property called MyProp)
Having said all that, it is still possible for one view model to subscribe to the events generated by the above methods, and if you really need to, you may of course.
EDIT:
Check this link for a description on item number 3 above.
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.
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.