Reputation: 135
The following is a simplified example of what I'm really trying to do, but my question about it is the same.
Suppose I have two objects, Man and Woman, both of which have the same Properties (Age, Height and Weight), but they are two distinct objects. I cannot change that.
Now suppose that I have a WPF panel, made using the MVVM principle that shows the Age of a certain Man in a textbox. I use Text="{Binding Path=OnePerson.Age}" for that, where OnePerson is an object of the type Man defined in the viewmodel.
This works fine, but I want a similar page to display this information of a Woman. Ideally, I'd like to just use the same view and viewmodel as before. But this is tricky, because the databinding points to the Man-object OnePerson. I can change the databinding programmatically (as described in WPF Binding Programatically), but I can only do so from the view's codebehind. I'm not allowed to do that, because we're using the MVVM model.
I would like to make OnePerson refer to either a Man or Woman object, but I don't know a good way to do so. They are different types, so I can't just assign either a Man or Woman using an if statement. I could declare OnePerson as an object instead of as a type, but then I can't access the Age, Height and Weight properties so easily anymore. Or I could make an entirely different ViewModel, one of which declares OnePerson as a Man and the other as a Woman, and use the same View for both of them. I think that should work, but it seems a bit weird to have two viewmodels for a single view. And adding my own Person class and translating between that and either Man or Woman would probably just make the whole viewmodel considerably more complicated when I start adding functionality like adding a new Man/Woman or editing an existing one, to the point where I might as well copy-paste the Man view and viewmodel and change only the OnePerson object to a Woman.
My question is, is there a clean and simple way to use a single View and Viewmodel to display the information of either a Man or a Woman in this case. Or should I not bother and make seperate pages for these cases?
Hope this is clear enough.
Upvotes: 2
Views: 1877
Reputation: 132548
I think this is more of a problem on the ViewModel
than the View or bindings. The View
is only meant to be a visual representation of the ViewModel
, and it sounds like your problem needs to be fixed in the ViewModel
, not the View
.
WPF's binding system only throws a warning if a binding is invalid, and it doesn't care data type the DataContext
is. The binding shown in your question ({Binding OnePerson.Age}
) will evaluate correctly regardless of if OnePerson
is a Man
or a Woman
object, and will display the value of any Age
property on that object.
Because of that, the best solution would be to make the OnePerson
property a type that could be either a Man
or a Woman
. An interface containing all the shared properties would be ideal because then it's properties can be accessed by the code without casting, and you can keep all the bindings you already have.
IPerson OnePerson { get; set; }
If it's not possible to use a shared interface than you could use an object
, however you'd need to remember to cast the OnePerson
object to a Man
or Woman
class before referencing it's properties in code.
object OnePerson { get; set; }
...
if (((Man)OnePerson).Age < 0) ...
A second alternative is to create two separate properties, one for Man
and one for Woman
. You could then access the properties of whatever item you're working with easily from the ViewModel
, and you could use a single View for the ViewModel.
Man SomeMan { get; set; }
Woman SomeWoman { get; set; }
You will probably need some flag to identify which is the populated object though, and to use this flag both for updating the object's properties and when you want to determine the View's DataContext
Here's an example of using a flag to determine the DataContext
<Style x:Key="MyContentControlStyle">
<Setter Property="Content" Value="{Binding SomeMan}" />
<Style.Triggers>
<DataTrigger Property="{Binding SelectedPersonType}" Value="Woman">
<Setter Property="Content" Value="{Binding SomeWoman}" />
</DataTrigger>
</Style.Triggers>
</Style>
And of course, if you do use a separate ViewModel
or want separate templates for each object type, you can easily do that using something like implicit DataTemplates
to determine how to draw each object.
<DataTemplate DataType="{x:Type myModels:Man}">
<myViews:ManUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type myModels:Woman}">
<myViews:WomanUserControl />
</DataTemplate>
<ContentPresenter Content="{Binding SelectedPerson}" />
Overall I'd recommend you either try to make OnePerson
be a data type that is shared by both objects, or you create a separate ViewModel
for both objects. It sounds like both your classes are very similar, so you might even be able to get away with some kind of generic ViewModel
, such as PersonViewModel<T>
, where you pass in Man
or Woman
as T
Upvotes: 3
Reputation: 22435
if you just want to use one xaml control for your age you can do
ageusercontrol
<TextBlock Text="{Binding Path=Age}" />
personview
<local:AgeUserControl DataContext="{Binding Path=MyObjectTypePersonProperty} />
Upvotes: 0
Reputation: 48139
Have you thought of making the object based on an Interfaced object. The interface basically creates a contract stating that anything derived from it must have whatever it has declared at a minimum... derived controls can have more, but at a minimum the things you want. For example.
public interface IPersonProperties
{
string PersonName { get; set; }
int Age { get; set; }
// if you want a function that is common between them too
bool SomeCommonFunction(string whateverParms);
etc...
}
public class Man : IPersonProperties
{
// these required as to support the IPersonProperties
public string PersonName { get; set; }
public int Age { get; set; }
public bool SomeCommonFunction(string whateverParms)
{ doSomething;
return true;
}
// you can still have other stuff specific to man class definition
public string OtherManBasedProperty { get; set;}
public void SomeManFunction()
{ // do something specific for man here }
}
public class Woman : IPersonProperties
{
// these required as to support the IPersonProperties
public string PersonName { get; set; }
public int Age { get; set; }
public bool SomeCommonFunction(string whateverParms)
{ doSomething;
return false;
}
// you can still have other stuff specific to WOMAN class definition
public string OtherWOMANBasedProperty { get; set;}
public void SomeWomanFunction()
{ // do something specific for man here }
}
Then, in your MVVM, the "object" that you can expose is that of an IPersonProperties such as
public class YourMVVM
{
public IPersonProperties BindToMe{ get; set }
public YourMVVM()
{
BindToMe = new Man();
// OR... BindToMe = new Woman();
}
}
Then, however / where ever in your MVVM object where you create your objects, do so. Your binding would be the same, yet could be of either gender. If something was common between them via the interface, you could reference that simply by
if( BindToMe.SomeCommonFunction( "testing"))
blah blah.
But, if in some method you need to do something based on a gender specific, you could do...
if( BindToMe is Man )
((Man)BindToMe).SomeManFunction();
else
((Woman)BindToMe).SomeWOMANFunction();
Hope this opens some doors for you on implementation choices.
Upvotes: 1