I am developing a ViewModel based on BindableBase.
This VM holds an instance of a domain model.
The VM exposes a property, say Name from which I want to not use local storage (i.e. storage in VM), but rather delegate to the model object's property.
I would like to use SetProperty(), but the storage reference cannot be a property.
Do I have to implement INotifyPropertyChanged my self ?
Is it at all a good idea to delegate to the model this way ?
Would it be possible to extend BindableBase (or team to add) to cover for this ?
So first you must chose how you will architect your VMs and Models. As you mentioned, there are a few options. The easiest and the way I recommend, is to just expose your Model as a property and then bind your View to the model properties:
public class MyViewModel : BindableBase
{
private Person _myPerson;
public Person Person
{
get { return _myPerson; }
set { SetProperty(ref _myPerson, value); }
}
}
If you don't want to do that and would rather wrap each individual model property, you would do it like this.
public class MyViewModel : BindableBase
{
private Person _myPerson;
private string _name;
public string Name
{
get { return _myPerson.Name; }
set { _myPerson.Name = value }
}
}
Keep in mind, your Person model object still has to implement INPC.
EDIT: If you don't have control over your models and need them to implement INPC, you could try to use IL weaving, or create a façade/decorator for your models and wrap them individually.
public class MyPersonFacade : BindableBase
{
private Person _myPerson;
private string _name;
public string Name
{
get { return _myPerson.Name; }
set
{
_myPerson.Name = value;
OnPropertyChanged();
}
}
}
Then use this as your Model in your VM.
Related
My app is a translation app. It contains a translation list that is passed to different viewmodel. Those viewmodels migth modify those lists including add and remove operations. For this purpose, I convert this list to an ObservableCollection in the constructor and my list is no longer modified. I know converting to an ObservableCollection creates a new object and the references are no longer the same. It is working perfectly for the concerned view, but once I want to change to another view, the list isn't updated. I was wondering what was the best way to solve this problem?
I thought I could create a custom ObservableCollection that would contain the corresponding list and automatically update it when an add or remove operation would be done. Something that'd look similar to this.
View
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// this is where I instanciate the viewModel, and the
// list<Translation> isn't modify once I close the view
DataContext = new ModifyWordVM(translations);
}
}
ViewModel
public class ModifyWordVM: INotifyPropertyChanged
{
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get { return translations; }
set { translations = value; OnPropertyChanged("Translations"); }
}
public ModifyWordVM(List<Translation> translations)
{
// Converting list to ObservableCollection
Translations = ConvertionHelper.ConvertTo(translations);
}
}
I'd like to know what is the cleaner way to get the modified list back.
You should encapsulate the traslations and their operations. To do this just introduce a class e.g. TranslationService which is shared between all relevant view models. To omit a smelly Singleton I added an instance of the service to the App.xaml resources.
The idea is that all modifications of the translation list take place in one location or type. The same type that is the binding source for the view. When adding a new translation the view should invoke a ICommand on the view model. This command will invoke the AddTranslation method on the TranslationService. Same for remove. Any changes to the translation collection will now reflect across the application.
If you also want to catch modifications of the actual translations (e.g. rename or edit) the TranslationService need to handle the PropertyChanged event of the ObservableCollection items as well.
When an items property changed the TranslationService must respond by raising the PropertyChanged event for the ObservableCollection property Translations. This would require the items to implement INotifyPropertyChanged too.
App.xaml
Shared TranslationService instance
<Application.Resources>
<TranslationService x:Key="TranslationService">
<TranslationService.DatabaseService>
<DatabaseService />
</TranslationService.DatabaseService>
</TranslationService>
</Application.Resources>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// Instantiate the view model and initialize DataContext from XAML instead.
// This method became redundant.
}
}
MainWindow.xaml
<Window.DataContext>
<ModifyWordVM>
<ModifyWordVM.TranslationService>
<!-- Reference the shared instance -->
<StaticResource ResourceKey="TranslationService" />
</ModifyWordVM.TranslationService>
</ModifyWordVM>
</Window.DataContext>
ModifyWordVM.cs
public class ModifyWordVM: INotifyPropertyChanged
{
public ModifyWordVM()
{}
public AddTranslation(Translation translation) => this.translationService.AddTranslation(translation);
public RemoveTranslation(Translation translation) => this.translationService.RemoveTranslation(translation);
public TranslationService TranslationService {get; set;}
public ObservableCollection<TranslationVM> Translations => this.translationService.Translations;
}
TranslationService.cs
public class TranslationService
{
public TranslationService()
{}
public AddTranslation(Translation translation)
{
// Add translations
}
public RemoveTranslation(Translation translation)
{
// Remove translations
}
private DatabaseService databaseService;
public DatabaseService DatabaseService
{
get => this.databaseService;
set
{
this.databaseService = value;
this.Translations = databaseService.getTranslations;
}
}
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get => this.translations;
set
{
this.translations = value;
OnPropertyChanged("Translations");
}
}
}
I've been using MVVM Light for a while now - it's extremely useful and almost always the first library I add to a new project!
I'm wondering what the implications would be of developing a class that implements INotifyPropertyChanged to encapsulate a bindable property (example below).
public class BindableProperty<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T mValue;
public T Value
{
get { return mValue; }
set
{
if (!EqualityComparer<T>.Default.Equals(mValue, value))
{
mValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
}
public BindableProperty(T default_value)
{
mValue = default_value;
}
}
With this class, I have to change my Xaml, but I believe my ViewModel might be more readable (below) - especially when the number of properties grows.
<TextBox Text="{Binding FirstName.Value, UpdateSourceTrigger=PropertyChanged}"/>
public class MainVM
{
public BindableProperty<string> FirstName { get; private set; }
public BindableProperty<string> LastName { get; private set; }
public MainVM()
{
FirstName = new BindableProperty<string>("");
LastName = new BindableProperty<string>("");
}
}
I know MVVM Light is designed to be extremely flexible, lightweight, and provide complete control (which it does very well). I can of course combine implementations and use the BindableProperty class above for some properties and more explicit ViewModelBase code for other properties in more complex situations.
Am I missing something obvious? What are some of the trade offs for this design that I might not be aware of (e.g. implications of changing xaml binding, data validation...)?
There is no extra value in encapsulating a property. This may work for very simple scenarios, but as soon as your ViewModel becomes more complex, you'll end up wiring your encapsulated classes in odd ways.
This approach won't work very well with validation neither it will if you have properties that depend on each other, i.e. FirstName, LastName and FullName where FullName is just a public string FullName { get { return FirstName+" "+LastName; } }.
Same applies for validation (with IDataErrorInfo). With the base class your code looks like
public string FirstName
{
get { return firstName; }
set
{
if(string.IsNullOrEmpty(value))
{
// where errors is a Dictionary<string, string>
errors.Add(nameof(FirstName), "First name can't be empty.");
return;
}
if(value.Length <2)
{
errors.Add(nameof(FirstName), "First name must be at least 2 characters long.");
return
}
Set(ref firstName, value);
errors.Remove(nameof(FirstName));
}
}
This will be a pain to implement in encapsulated properties
Here's a simplified ViewModel:
public class EditViewModel : BaseViewModel
{
private Item _currentItem;
public Item CurrentItem
{
get
{ return _currentItem; }
set
{
if (_currentItem != value)
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
}
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties = value;
OnPropertyChanged("ItemProperties");
}
}
public void AddProperty() //this is called from an ICommand
{
Property p = new Property{ ItemId = CurrentItem.ItemId };;
CurrentItem.Properties.Add(p);
ItemProperties.Add(p);
}
}
What I'd like to do is to separate out the business logic here into a separate class. It keeps all the annoying MVVM boilerplate out of the way of the useful stuff, and in theory should lead to organizing the code into a more testable state.
We're starting to do this by creating separate "Logic" classes which inherit from BaseViewModel and then have the actual ViewModels inherit from their logic class. So:
public class EditLogic : BaseViewModel
{ }
public class EditViewModel : EditLogic
{ }
Then the logic goes in the logic class.
For some business logic this separation is simple - nice and clean. However, in the example I've given above I can't see a simple way of pulling that method out without a lot of unnecessary faff. Something like this (untested):
public class EditLogic : BaseViewModel
{
public Property GetNewProperty(Item currentItem)
{
Property p = new Property{ ItemId = currentItem.ItemId };
currentItem.Properties.Add(p);
return p;
}
}
public class EditViewModel : BaseViewModel
{
public void AddProperty() //this is called from an ICommand
{
ItemProperties(GetNewProperty(CurrentItem))
}
}
This seems potentially confusing - since it's relying on CurrentItem implicitly being passed by reference - and unnecessarily convoluted to no great gain.
This is, of course, a very simple example which isn't worth fussing over. But it illustrates the point that in MVVM it's very easy to end up mixing your presentation/binding code with your business logic for the sake of convenience.
I could move some of the properties out from the EditViewModel to the EditLogic but then we're losing the advantages of separating these two out in the first place.
So: is it worth bothering with this at all? If so, how far should we pursue it? And are there any better methods for maintaining separation?
What you are looking for are services.
public interface IPropertyService
{
Property GetNewProperty(Item currentItem);
}
You will of course need an implementation:
public class MyPropertyService : IPropertyService
{
public Property GetNewProperty(Item currentItem)
{
//TODO
}
}
You can then inject this service into the constructor of your view model as a dependency.
public class MyViewModel
{
private IPropertyService _PropertyService;
public MyViewModel(IPropertyService propertyService)
{
_PropertyService = propertyService;
}
public void AddProperty() //this is called from an ICommand
{
Property p = _PropertyService.GetProperty(CurrentItem);
CurrentItem.Properties.Add(p);
ItemProperties.Add(p);
}
}
This will ensure that you don't need to create a myriad of view model base classes for your business logic. Instead, encapsulate your business logic in services and pass them into view models that depend on them.
I’m having a weird & frustrating problem passing an object between some of my classes. This stems from the fact I am a scripter and not a programmer, and am just bumbling along. So I’m sure I’m doing something dumb :)
I am trying to build a Wizard dialog which has multiple pages. I am using the “Internationalised WPF Wizard” tutorial from CodeProject as a starting point, and attempting to adapt it to my domain. I’m getting stuck because my wizard pages can’t seem to refer to the model.
I have done the following:
Created a class for my Model (let call this MyData)
Created a base class for my view models (ViewModelBase)
Created a view model class for each of my pages, inheriting from ViewModelBase (example below is WelcomePageViewModel)
Created a ‘controller’ style view model which drives the wizard. (WizardController)
When the wizard is launched, WizardController is instantiated. WizardController also instantiates MyData.Then, WizardController instantiates each of the view models for the remaining pages.
The actual GUI seems to work fine, and I can see that the view models for each of the pages are being loaded correctly. Here’s some code:
public class MyData
{
private string _someString;
public MyData(string someString)
{
_someString = someString;
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
readonly MyData _myData;
public ViewModelBase(MyData myData)
{
_myData = myData;
}
}
public class WizardController : INotifyPropertyChanged
{
MyData _myData;
public WizardController()
{
_myData = new MyData("The Widgets");
}
}
public class WelcomePageViewModel : ViewModelBase
{
private MyData _myData;
public WelcomePageViewModel(MyData myData)
: base(myData)
{
_myData = myData;
// accessing _myData fails :(
MyLogger.WriteLine("Grabbed an instance of myData: " + _myData.ToString());
}
}
However, my code fails when I try to access myData from WelcomePageViewModel. On the MyLogger line in WelcomePageViewModel, the error “Object reference not set to an instance of an object.” is thrown.
Basically, all I’m trying to achieve is WizardController setting up MyData, and each of the wizard pages being able to access (and manipulate) it. So any guidance would be greatly appreciated!
As Rob G suggested in a comment, you're re-declaring the variable _myData in the inherited classes. The correct way to organize your code is to let the _myData be a protected property declared only on the abstract base class, and use this property to access the variable from the inheriting classes.
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
protected MyData MyData { get; private set; }
public ViewModelBase(MyData myData)
{
MyData = myData;
}
}
public class WelcomePageViewModel : ViewModelBase
{
public WelcomePageViewModel(MyData myData)
: base(myData)
{
// Access the protected property
MyLogger.WriteLine("Grabbed an instance of myData: " + MyData.ToString());
}
}
Edit: fixed a copy-paste error...
There seems to be conflicting thoughts on whether INotifyPropertyChanged should be implemented in the Model or not. I think that it should be implemented in the ViewModel, but I can't figure out how it would be accomplished. There are plenty of mentions of this same idea all over stackoverlow.com ( In MVVM model should the model implement INotifyPropertyChanged interface?, In MVVM should the ViewModel or Model implement INotifyPropertyChanged?), but I can't find any example to show how to do it.
Let's say for example I have a model Person:
Public Person {
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public void NameChange( string newName );
}
How would I implement the ViewModel so that changes in Age, FirstName, or LastName are all recognized?
Public PersonViewModel : INotifyPropertyChanged {
Person _person;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName) {
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//ctor, Properties, etc...
}
EDIT - Clarification:
So without changing the Person model how do I modify the ViewModel to get notified of the updates?
Is that even possible? If not, how are those that subscribe to the "INPC in the model is baaaad" get notified of changes in the model?
ViewModel should definitely implement INotifyPropertyChanged. I don't have a strong opinion on whether it should be implemented in the Model as well. I don't think you need it when the model properties don't change independently from the ViewModel while it is bound to the View.
Anyway, this is how I'd implement INotifyPropertyChanged in the ViewModel when it is not already implemented in the Model:
public class PersonViewModel : INotifyPropertyChanged
{
private Person person;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public PersonViewModel(Person person)
{
this.person = person;
}
public int Age
{
get { return person.Age; }
set
{
if (value != person.Age)
{
person.Age = value;
OnPropertyChanged("Age");
}
}
}
public string FirstName
{
get { return person.FirstName; }
set
{
if (value != person.FirstName)
{
person.FirstName = value;
OnPropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return person.LastName; }
set
{
if (value != person.LastName)
{
person.LastName = value;
OnPropertyChanged("LastName");
}
}
}
}
Seeing how you updated you question, I need to add that without having INotifyPropertyChanged (or a similar custom notification event) implemented in the model, you can't get notified about the changes in the model that happen in it independently from the ViewModel. I guess you should be able to avoid that. Otherwise just implement INotifyPropertyChanged in it. There's nothing wrong with that if you need it.
Interesting question. I've read about MVVM for more than a year now, and I'm still not sure about it.
If your application is representing a state of a process for example, and this state is modified internally without any interaction of the user, then your model needs to be able to notify your viewmodel that it changed.
So if your model implement INotifyPropertyChanged, and your viewmodel only pass the same informations to the view, then... does your viewmodel really need to exist...?
In our company, we consider two main cases:
We structure our software with a quite strict UML analysis before developping (not so agile). When we then want to display our objects on screen, they return us their different views, which are used when needed with Bindings (using ContentControl or so). Most of the views we need for our software display these kinds of object, that implement INotifyPropertyChanged and are therefore also kind of ViewModels.
To build the software main Views (view structure), we create global views and ViewModels for them. That's when we really follow the MVVM practices.
Maybe I missed a point about MVVM, but in my experience, it's not a pattern that you absolutely have to always follow. It's a very good way of thinking to develop WPF applications, but creating ViewModels for each and every view seems to me like a big overhead.
What do all of you think of this way of doing?
Best regards,
Antoine
EDIT 31.03.2012
I have found a very interesting article explaining how to handle your model properties in the viewmodel, without having to implement a proxy property in the viewModel for each one of them.
Also the writer say some words about having INPC implemented in the model, and the viewmodel listening to it.
I think this is the most practical oriented article I've read about MVVM so far.
Check it out :
http://msdn.microsoft.com/en-us/magazine/ff798279.aspx
In my experience, Model objects don't have to (and probably shouldn't) know that they are being constructed in a View. Often, Model objects are entities that should never be allowed to be in an invalid state. ViewModel objects are the things that construct the Model objects.
So, since you never want to create a person who is very old or very young, and every person needs a name, your Person class might look like this:
public class Person {
public int Age { get; private set; }
public string Name { get; private set; }
public Person(int age, string name) {
if (age < 0 || age > 150) throw new ArgumentOutOfRangeException();
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException();
Age = age;
Name = name;
}
}
And your PersonViewModel might look like this::
class PersonViewModel : INotifyPropertyChanged {
private int _Age;
private int _Name;
public int Age {
get { return _Age; }
set {
if (_Age.Equals(value)) return;
_Age = value;
RaisePropertyChanged("Age");
}
}
public string Name {
get { return _Name; }
set {
if (_Name.Equals(value)) return;
_Name = value;
RaisePropertyChanged("Name");
}
}
public Person CreatePerson() {
return new Person(_Age, _Name);
}
}
You can then put whatever values you want in your PersonViewModel without worrying about creating an invalid Person object. You can also implement validation in the PersonViewModel that may be more strict than the validation in the Person class (for example, restricting the age range to adults over 18 years old (see IDataErrorInfo)).
Save for the typos you pretty much have it ;)
All you would need to add is your constructor and property definitions:
public class PersonViewModel : INotifyPropertyChanged
{
Person _person;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public PersonViewModel(Person person)
{
_person = person;
}
public int Age
{
get
{
return _person.Age;
}
set
{
_person.Age = value;
OnPropertyChanged("Age");
}
}
}
If you have a choice, I would definitely recommend implementing INotifyPropertyChanged in the Model because you won't havae to worry about translating Models to ViewModels and back.
But if you can't, see above :)