Immortal Blue
Immortal Blue

Reputation: 1700

dynamically display different View for different ViewModel but the same attribute

Ok, I'm trying to get to grips with MVVM. I have an application that has multiple options for image capture. Depending on the mode, the image is either loaded from an existing file, or captured from a camera.

I'm writing a page using the MVVM pattern which represents the configuration of the image capture device.

The model consists of two classes which expose the specific (and non common) values for each of the modes which conform to a common interface of IImageSource.

Each of the two model classes have a contextually defined viewmodel:

and two corresponding views.

The model has an attribute which returns IImageSource.

I'm currently using third view, ImageSourceView as the page. I'm handling the loading event which gets the value from the model, then, depending on the type will instantiate the correct viewmodel and the correct view to go with it and then adds that as it's content. However, this seems to be going against the spirit of MVVM in that I've now written some decision code in the code behind

Is there a more elegant/ better way of determining which viewmodel/ view should be instantiated and used?

Upvotes: 2

Views: 2707

Answers (3)

sll
sll

Reputation: 62544

You have two palces where you need decide which type to use in run time:

  1. ViewModel
  2. View

On ViewModel level just use ViewModel factory, so just by an EventType/ValueType instantiates an appropriate ViewModel:

private IImageSourceViewModel ProcessEvent(IEvent someEvent)
{
    return viewModelFactory.Create(someEvent.Type)
}

Then on View level just use DataTemplateSelector which accepts via binding already resolved ViewModel instance and then decides which View to use:

MainView XAML:

<ContentControl 
   Content="{Binding ImageSourceViewModel}"    
   ContentTemplateSelector = 
      "{StaticResource ImageSourceViewDataTemplateSelector}">
</ContentControl>

ImageSourceViewDataTemplateSelector:

private sealed class ImageSourceViewDataTemplateSelector: DataTemplateSelector
{
    public ImageSourceViewDataTemplateSelector(... dependencies if any...)
    {             
    }
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        DataTemplate dataTemplate = null;
        IImageSourceViewModel instance = item as IImageSourceViewModel;

        // move out into the constructor
        var dataTemplateFactory = new Dictionary<Type, Func<DataTemplate>>
            {
                    { typeof(ICameraSourceViewModel), (x) => this.Resources["CameraSourceDataTemplate"] as DataTemplate }, 
                    { typeof(IFileSourceViewModel), (x) => this.Resources["FileSourceViewModel"] as DataTemplate }
            };

        // TODO: handle not supported type case yourself

        return dataTemplateFactory[instance.GetType()]();
    }
}

Upvotes: 1

hbarck
hbarck

Reputation: 2944

Actually, you shouldn't need a TemplateSelector, since the two ViewModels will have different types. You can declare DataTemplates in XAML as resources with the model type as key, so that WPF chooses the correct DataTemplate automatically:

  • Have a main ViewModel, which exposes a ImageSourceViewModel property. This property would either return a CameraSourceViewModel or a FileSourceViewModel, as appropriate.
  • In your page, the DataContext would be the main ViewModel, and you'd have XAML like this:

Code Example:

<Page x:Class="Page1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
  xmlns:my="clr-namespace:WpfApplication1"
  mc:Ignorable="d" 
  d:DesignHeight="300" d:DesignWidth="300"
  Title="Page1">
<Page.Resources>
    <DataTemplate DataType="{x:Type my:CameraSourceViewModel}">
        <my:CameraSourceView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type my:FileSourceViewModel}">
        <my:FileSourceView/>
    </DataTemplate>
</Page.Resources>
<Grid>
    <ContentControl Content="{Binding ImageSourceViewModel}"/>
</Grid>
</Page>

Upvotes: 4

ken2k
ken2k

Reputation: 48995

Here's some idea about what you could do:

Have a ImageSourceViewModel, that is the ViewModel of your ImageSourceView view. It would be the role of this viewModel to get "your value" from the model, and expose it as a public property of type IImageSource.

Then, in your ImageSourceView view, you could use a template selector to change the content of the view, depending on the concrete type of the exposed IImageSource property.

See http://www.codeproject.com/Articles/418250/WPF-Based-Dynamic-DataTemplateSelector

Upvotes: 1

Related Questions