2

I have created the following model (the code is simplified to illustrate the situation):

public abstract class Account
{
    public string Name { get; set; }
}

public class Person : Account
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Company : Account
{
    public string Owner { get; set; }
}

Next I have created a view model:

public class ViewModel
{
    public Account Model { ... }
    public string Name { ... }
    public string FirstName { ... }
    public string LastName { ... }
    public string Owner { ... }   
    ...
}

And finally, the view:

<UserControl>
  <UserControl.Resources>

    <!-- Person data template -->
    <DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
      <Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
        <TextBlock Text="{Binding Path=Name}" />
        <TextBlock Text="{Binding Path=FirstName}" />
        <TextBlock Text="{Binding Path=LastName}" />
      </Grid>
    </DataTemplate>

    <!-- Company data template -->
    <DataTemplate x:Key="companyTemplate" DataType="{x:Type model:Company}">
      <Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
        <TextBlock Text="{Binding Path=Name}" />
        <TextBlock Text="{Binding Path=Owner}" />
      </Grid>
    </DataTemplate>

    <!-- Data template selector for different account types -->
    <local:AccountTemplateSelector x:Key="templateSelector" 
        PersonTemplate="{StaticResource personTemplate}" 
        CompanyTemplate="{StaticResource companyTemplate}" />

  </UserControl.Resources>

  <StackPanel Name="rootLayout" DataContext="{Binding Path=viewModel}">
    <ContentControl Content="{Binding Path=Model}" 
        ContentTemplateSelector="{StaticResource templateSelector}"/>
    <Button Content="Save" />
    <Button Content="Close" />
  </StackPanel>

</UserControl>

So, when the model that is loaded is of type Person the personTemplate is shown; vice versa, when the model is Company the companyTemplate is shown.

My questions are:

  1. Does this approach make sense at all? Would it be smarter to delete the Model property in the ViewModel class and to introduce an enum or just a simple bool which would show person if true, or company if `false?
  2. While defining the data templates, I specified DataTypes to Person and Company types (it was natural to me to do it this way). Do I need it at all because in the very next line I am setting a new data context to be the one from the UserControl?
  3. Should the DataTypes of the data templates be different view models, something like PersonViewModel and CompanyViewModel? Does it make sense to create them?
  4. How can I, and can I at all, make data template inherit the data context from the ContentControl automatically?

I know that all this is a matter of a personal choice in the end, but since I am learning MVVM (I am using MVVM Light), I am wondering which approach would be the most recommendable one? I still do not fully understand when should the classes from models be used as data types for data templates and when should view models be used for that purpose. Should the assembly that represents the model even be referenced in the view assembly (assuming that view, model and view model all reside in separate assemblies)?

Thanks for all the clarifications!

UPDATE:

This update should explain the problem of having classes of the model as DataTypes in the data templates when the property of the model class is not directly binded to just one control in the view.

There is an enum and a new property in the Person, so now it looks like this:

public class Person : Account
{
    public enum GenderType { Female, Male, NotSpecified }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public GenderType Gender {get; set; }
}

And in the view, the data template of the person is changed as well of course:

<!-- Person data template -->
<DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
  <Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
    <TextBlock Text="{Binding Path=Name}" />
    <TextBlock Text="{Binding Path=FirstName}" />
    <TextBlock Text="{Binding Path=LastName}" />
    <RadioButton Name="Female" />
    <RadioButton Name="Male" />
    <RadioButton Name="NotSpecified" />
  </Grid>
</DataTemplate>

If the Content of the ContentControl is set to Model property of the ViewModel, how would I resolve the gender/radio buttons situation; because, now they do not match in the way one control/one property?

H.B.
  • 166,899
  • 29
  • 327
  • 400
Boris
  • 9,986
  • 34
  • 110
  • 147
  • @vorrtex: Good point! Actually, the solutions provided below implicitly upset that question as well. Thanks for joining in. – Boris Apr 18 '11 at 18:17
  • 1
    1) I would remove 4 other properties than the Model property. 3) It is possible to have several data templates for a single view model. 4) They inherit it auomatically. Update question: use the ListBox and change the ItemContainerStyle property to looks like the RadioButton. – vortexwolf Apr 18 '11 at 18:19
  • Voted up for the list box that looks like the panel with radio buttons idea. – Boris Apr 18 '11 at 18:23
  • @Boris I have pressed the Enter button occasionally. Anyway I wanted to say almost the same that Markus said: remove unnecessary properties and binding to the rootLayout. – vortexwolf Apr 18 '11 at 18:25
  • @Boris If you like this idea, I can share the code of this template which I used in my applications: http://pastebin.com/5nrJQNj2 Set it like this: `ItemContainerStyle="{StaticResource RadioButtonItemContainerStyle}"`. But it is from Silverlight so I don't know how it will work in WPF. – vortexwolf Apr 18 '11 at 18:33
  • @vorrtex, you wrote "3) It is possible to have several data templates for a single view model". I am considering this approach very much now. How would that work? Let's say that my view model will only contain `Model` property. If I set that vm to be the `ContentControl`'s content, and also a `DataType` for both data templates, how would the app distinguish which data template to use? Right now, choosing appropriate data template is based on the type inherited from the `Account`. In case of having single vm as `DataType`, I would lose that capability. – Boris Apr 20 '11 at 10:13
  • @Boris For example, you have a view model with the ModelType property of the enum type. Then you should write several data templates with the defined property `x:Key` (not implicit, as in your first code block), create a class which inherits the `DataTemplateSelector` class and switch the necessary template based on the value of the ModelType property. In this example there is only one view model type, but many enum types. – vortexwolf Apr 20 '11 at 10:32

2 Answers2

2

I would change it to this:

<UserControl>
  <UserControl.Resources>
    <!-- Person data template -->
    <DataTemplate DataType="{x:Type model:Person}">
      <Grid>
        <TextBlock Text="{Binding Path=Name}" />
        <TextBlock Text="{Binding Path=FirstName}" />
        <TextBlock Text="{Binding Path=LastName}" />
        <RadioButton Name="Female"       IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Female}" />
        <RadioButton Name="Male"         IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Male}" />
        <RadioButton Name="NotSpecified" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=NotSpecified }" />
      </Grid>
    </DataTemplate>

    <!-- Company data template -->
    <DataTemplate DataType="{x:Type model:Company}">
      <Grid>
        <TextBlock Text="{Binding Path=Name}" />
        <TextBlock Text="{Binding Path=Owner}" />
      </Grid>
    </DataTemplate>
  </UserControl.Resources>

  <StackPanel DataContext="{Binding viewModel}">
    <ContentControl Content="{Binding Model}" />
    <Button Content="Save" />
    <Button Content="Save" />
    <Button Content="Close" />
  </StackPanel>

</UserControl>

like this you define implicit styles for your classes and you don't have to use a templateselector. Also then you don't need all your string properties in the ViewModel class:

public class ViewModel
{
    public Account Model { ... } 
    ...
}

Disclaimer, the binding in the RadioButtons uses a Converter from here.

Community
  • 1
  • 1
Markus Hütter
  • 7,796
  • 1
  • 36
  • 63
  • @Markus: Thank you for your answer. What happens if I have a `Birthday` property in the `Person` class and I want it shown like this: "Born on " - is it better to create a property for it in the view model and to format/convert it there (which would make problem with "clean" data templates), or to create a Converter for it? Thanks. Oh, and lets assume that using `StringFormat` is out of question here for reasons far to long and specific to explain here. – Boris Apr 18 '11 at 17:44
  • @Boris you would define the property on the viewmodel like: `DateTime Birthday`. Then I would use StringFormat or a Converter to display it. – Markus Hütter Apr 18 '11 at 17:53
  • @Markus: Thanks again. It appears that the example with `Birthday` does not illustrate my concern well enough. That's why I added a little update to my question to explain the problem I am facing. I like your idea with supplying `Model` to the `Content` property of the `ContentControl` VERY MUCH! The only problem is, I am not sure how to handle the gender property. If you could assist me with this one, that would be just perfect! – Boris Apr 18 '11 at 17:57
  • @Boris to accommodate for your update I would rather suggest using a combobox for selection of Female/Male stuff like shown [here](http://bea.stollnitz.com/blog/?p=28) – Markus Hütter Apr 18 '11 at 17:59
  • @Boris, also if you really want to use RadioButtons check out the answers to [this question](http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum) – Markus Hütter Apr 18 '11 at 18:09
  • @Markus: If I don't come up with a sleek solution to this issue with `Gender` property (or somebody else), I am afraid I will have to satisfy myself with replacing radio buttons with combo box. Thanks once again for helping me out! – Boris Apr 18 '11 at 18:11
  • @Markus: Yeah, I thought of the converter as well. It simply makes the most sense to me. The only reason why I asked about this in the first place is that I've read somewhere that MVVM is supposed to void the need to create converters. I guess it actually DOES make sense to create one, for situations like these :) – Boris Apr 18 '11 at 18:14
  • @Boris updated my question with the converter I linked to. And well you can always define Properties on the ViewModel for not creating a ValueConverter but in cases like this a Converter makes more sense! – Markus Hütter Apr 18 '11 at 18:16
0

Absolutely positively not (are they obsolete).

  1. Yes, it makes perfect sense, however your bindings, not so much. Those issues can be handled in a number of different ways. For instance, the Account could have a Parent property which exposes the ViewModel it is contained in (I didn't say it was the best approach).
  2. This is an issue with your ViewModel design, where you must bind not against the Account but the ViewModel. It might be possible to change the design so that you don't need to do this; hard to tell with the snapshot you have provided.
  3. I don't think that will help you at this point. I'd see if there was a way to keep your ViewModel related UI out of the DataTemplate.
  4. Here's one good solution: Create a DataTemplateSelector that chooses the template based on the Account property. That way, you can bind the ItemsSource directly to the DataContext, and it will be available within the DataTemplate.
  • Thank you for your answer. I added a little update to my question. So now, if I bind against the `Account`, how would I handle the `Gender` property, because I wouldn't be able to just bind Gender to the 3 radio buttons. Or if I perhaps would, could you explain to me how? Thank you! – Boris Apr 18 '11 at 18:01