Convention-based view model registration

When you create a ListBox or ItemsControl, you usually specify an ItemTemplate to render each of the items. You can do so directly in the ItemsControl itself.

<ListBox ItemsSource="{Binding Projects}" SelectedItem="{Binding SelectedProject}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Or you can identify a resource by name.

<UserControl.Resources>
    <DataTemplate x:Key="ProjectItemTemplate">
        <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
</UserControl.Resources>

<ListBox
    ItemsSource="{Binding Projects}"
    SelectedItem="{Binding SelectedProject}"
    ItemTemplate="{StaticResource ProjectItemTemplate}"/>

Either of these methods causes every item to be rendered in the same way. But what if different kinds of items are represented with different views? If you want each item to be rendered according to its type, you can add all of the data templates to the resource dictionary. The list will select the data template based on the item type.

<UserControl.Resources>
    <DataTemplate DataType="{x:Type projects:SoftwareProject}">
        <StackPanel>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text="{Binding LinesOfCode}"/>
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type projects:HardwareProject}">
        <StackPanel>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text="{Binding ServerCount}"/>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

<ListBox
    ItemsSource="{Binding Projects}"
    SelectedItem="{Binding SelectedProject}"/>

Convention over configuration

You can explicitly add data templates to a resource dictionary if you have only a few closely related types to choose from. But explicit configuration becomes tedious if this set of types changes frequently, if there are many of them, or if they are located in different namespaces. In some cases, it is easier to adopt a convention-based approach.

I started the WPF LOB project on Codeplex to experiment with patterns for line-of-business applications in WPF. One of those patterns is switching among views within the same window. The project defines a base class for view models that represent panes within the window. It uses convention over configuration to associate those view models with their views.

The main form looks for all classes that inherit from Pane and end with the words “ViewModel”. By convention, the view model will be located in a namespace called “ViewModel”. The associated view will be a user control located in a namespace called “View”, and ending with the word “View”. The main form applies that convention to locate the view, and adds a data template to the resource dictionary.

public MainWindow()
{
    InitializeComponent();
    RegisterAllPanes();
}

private void RegisterAllPanes()
{
    // Find all pane view model types in the assembly.
    Assembly assembly = Assembly.GetExecutingAssembly();
    foreach (Type viewModelType in assembly.GetTypes())
    {
        if (typeof(Pane).IsAssignableFrom(viewModelType) && viewModelType.Name.EndsWith("ViewModel"))
        {
            string viewTypeName = viewModelType.FullName.Replace("ViewModel", "View");
            Type viewType = assembly.GetType(viewTypeName);
            if (viewType != null)
                RegisterDataTemplate(viewModelType, viewType);
        }
    }
}

private void RegisterDataTemplate(Type viewModelType, Type viewType)
{
    DataTemplate dataTemplate = new DataTemplate(viewModelType)
    {
        VisualTree = new FrameworkElementFactory(viewType)
    };
    Resources.Add(dataTemplate.DataTemplateKey, dataTemplate);
}

Caliburn

Rob Eisenberg created an MVVM framework called Calliburn that relies heavily on convention over configuration. His framework goes so far as to data bind controls to properties based on convention, rather than configuring each using the “{Binding}” markup extension. Watch his presentation from Mix10. If you would like to develop applications within the realm of his opinion, I recommend investing the time in learning Caliburn. But even if you don’t, you can still use simple techniques like the one above to take advantage of convention over configuration when it makes the most sense.

Leave a Reply

You must be logged in to post a comment.