MVVM (with WPF) - Binding Multiple Views to the Same ViewModel
Asked Answered
H

4

16

I have recently started investigating the MVVM pattern with WPF for an upcoming project. I started with Josh Smith's MSDN article. I have a question (well many, but let's start with one):

I have an IndividualViewModel which exposes the properties of the model. I need two views "Add Individual" and "Edit Individual" which are very similar as you can imagine. What I have done currently is to have 2 subclasses AddIndividualViewModel and EditIndividualViewModel which expose the Add and Edit commands respectively. I also have 2 similary named views that bind to these.

Now this method works and these classes are fairly small anyway, but I'm wondering if it is possible for me to have just the one view model, which exposes both commands. I would still have 2 views which would bind to this same view model, exposing the appropriate command as a button. I'm not quite sure how to do this. In the main window resources I have something like:

        <DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
            <Views:AddIndividualView />
        </DataTemplate>

With this method of binding you can only have a one-to-one binding, i.e. the same view is always shown for a given view model. Is there a way to automatically switch the view depending on a property on the view model (e.g. IndividualViewModel.Mode). Is there a different approach I should be considering?

Note that the main window has a collection of view models and shows each in tab.

Thank you!

Hydrogenize answered 1/1, 2010 at 14:15 Comment(0)
D
6

So you need 2 different views based on a property value. One thing to consider is to refactor your presentation code, so instead of the values of a property you could have real subclasses. Then you can use 2 different DataTemplate for each class.

<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
  <Views:AddIndividualView />
</DataTemplate>

<DataTemplate DataType="{x:Type ViewModels:EditIndividualViewModel}">
  <Views:EditIndividualView />
</DataTemplate>

If you think that is an overkill, you could use a trigger and wrap your specific views into a ContentPresenter.

<DataTemplate x:Key="AddIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
  <Views:AddIndividualView />
</DataTemplate>

<DataTemplate x:Key="EditIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
  <Views:EditIndividualView />
</DataTemplate>

<DataTemplate DataType="{x:Type ViewModels:IndividualViewModel}">
  <ContentPresenter Content="{Binding}">
    <ContentPresenter.Style>
      <Style TargetType="ContentPresenter">
        <Setter Property="ContentTemplate" Value="{StaticResource AddIndividualTemplate}" />
        <Style.Triggers>
          <DataTrigger Binding="{Binding Mode}" Value="{x:Static ViewModels:IndividualMode.Edit}">
            <Setter Property="ContentTemplate" Value="{StaticResource EditIndividualTemplate}" />
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </ContentPresenter.Style>
  </ContentPresenter>
</DataTemplate>
Dorkas answered 11/3, 2015 at 13:8 Comment(1)
This is the classic MVVM solution. It specifies both of the options and has no code behind.Handcraft
H
4

Thanks for pointing me in the right direction! I am still new with WPF too and learning about all the different possibilities including binding methods. Anyway for anyone interested, here is the solution I arrived at for this particular case:

I decided I wanted to keep the view models separated in two subclasses AddIndividualViewModel and EditIndividualViewModel which only expose commands, rather than trying to manage state in the one class. However I wanted one view so that I'm not duplicating the XAML. I ended up using two DataTemplates and DataTemplateSelector to switch out the action buttons depending on the view model:

        <DataTemplate x:Key="addTemplate">
            <Button Command="{Binding Path=AddCommand}">Add</Button>
        </DataTemplate>

        <DataTemplate x:Key="editTemplate">
            <Button Command="{Binding Path=UpdateCommand}">Update</Button>
        </DataTemplate>

        <TemplateSelectors:AddEditTemplateSelector
            AddTemplate="{StaticResource addTemplate}"
            EditTemplate="{StaticResource editTemplate}"
            x:Key="addEditTemplateSelector" />

and a content presenter at the bottom of the form:

        <ContentPresenter Content="{Binding}"
                          ContentTemplateSelector="{StaticResource addEditTemplateSelector}" />

Here is the code for the template selector:

class AddEditTemplateSelector : DataTemplateSelector
{
    public DataTemplate AddTemplate { get; set; }
    public DataTemplate EditTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is AddIndividualViewModel)
        {
            return AddTemplate;
        }
        else if (item is EditIndividualViewModel)
        {
            return EditTemplate;
        }

        return null;
    }
}

This may or may not be how implement the final thing (given the requirements) but it's good to see I have this sort of option available.

Heredia answered 3/1, 2010 at 13:29 Comment(0)
E
0

There's no reason why you shouldn't be able to achieve that. One way of doing this is to provide some flag in your view model stating whether you're in add mode or in edit mode, and styling your view based on that flag using simple bindings, triggers or template selectors.

For reference you may look at Sacha Barber's DataWrapper class that's part of his Cinch framework (not directly applicable to your case, but it's a good starting point) which wraps data fields in the view model in such a way to support a flag to toggle between read only (view record mode), and read-write (edit record mode). You could apply a similar approach to make the distinction between add and edit.

Basically, instead of having simple properties in your view model, instantiate a data wrapper class which includes a Value property, and a IsAdding property. In your view, you can use bindings, triggers or template selectors to modify templates based on that property.

Eardrum answered 1/1, 2010 at 14:23 Comment(0)
I
0

For this task you do not need any DataTemplateSelector at all.

  1. Derive both EditIndividualVM and AddINdividualVM from IndividualVM.
  2. The Edit- and AddCommands route to a setter property in the IndividualVM.
  3. The setter VM = new AddIndividualVM or VM = new EditIndividualVM depending on which button is pressed.
  4. In xaml you bind in the contentgrid to your VM property like this:

Iberia answered 21/3, 2010 at 17:52 Comment(1)
It seems you have code that is missing. Could you update your answer with the code snippet please?Spectrochemistry

© 2022 - 2024 — McMap. All rights reserved.