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"));
}
}
}
精彩评论