Nikita B
Nikita B

Reputation: 3333

How to plug in an arbitrary view dynamically into xaml in MVVM application?

There are multiple places in my application whehe I have ContentControl placed in xaml and I do not know beforehand what its Content is going to be. What is the best practice to implement this scenario?

Right now I am considering two approaches:

  1. Bind ContentControl.Content to view model and use a dictionary of DataTemplates to find an appropriate view. My issue with this approach is that even if I were to list all the possible combinations in a dicitonary, in some cases I simply do not know an exact type of view (or viewmodel if there is any) at compilation time. I think, I am also going to have troubles using this approach for hosting non-WPF content.
  2. Create some sort of an interface:

    interface IContentPlugin : IDisposable
    {
        object View { get; }
    }
    

    and bind ContentControl.Content to IContentPlugin.View directly. I could then have multiple implementations of this interface and swap them when I need to. But this solution does not strike me as something that goes well with MVVM application, as it forces me to have references to IContentPlugins in my view models.

What do you think is the best option and why? Perhaps there is a better approach?

Upvotes: 0

Views: 495

Answers (4)

Nikita B
Nikita B

Reputation: 3333

Eventually, I went with second approach. I was able to solve my main problem, which was:

But this solution does not strike me as something that goes well with MVVM application, as it forces me to have references to IContentPlugins in my view models.

Passing those "plugins" into viewmodels was a mistake, you should not do it. What you can and should do is find a way to partition your view into smaller independent segments, and set their content in non-MVVM way. So basically I ended up with a view, which acted as container and looked like this:

<UserControl x:Name=this>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefiniton>
            <RowDefiniton>
            <RowDefiniton>
        </Grid.RowDefinition>
        <ContentControl Grid.Row="0" Content="{Binding PluginA.View, ElementName=this}"/>
        <ContentControl Grid.Row="1" Content="{Binding PluginB.View, ElementName=this}"/>
        <ContentControl Grid.Row="2" Content="{Binding PluginC.View, ElementName=this}"/>
    </Grid>
</UserControl>

where PluginA, PluginB and PluginC are dependency properties in code-behind, that are set by DI container using property injection. I am happy with the end-result, and it gives me the flexibility I need.

You can also use PRISM, which roughly speaking does the same thing, but in more general and flexible manner. It was somewhat too complex for my application though, so I decided to keep it simple. But you should give it a try, if you are trying to solve similar issue.

Upvotes: 0

toadflakz
toadflakz

Reputation: 7944

You should use implicit View determination via DataTemplates.

This is achieved by having type-specific DataTemplates (i.e. DataTemplates without a key reference) for your ViewModel types in a ResourceDictionary local to the scope of the ContentControl.

Be aware though that you will need to scope the ResourceDictionary quite carefully in the case where a single ViewModel can have multiple Views associated with it.

Update:

The reasons to use implicit View determination are:

  • In general, the View resolution look-ups are faster than if you were to write a View resolving service.
  • You're not duplicating effort by writing your own View resolver which you then need to plug into the WPF runtime.

You should be telling the external source what you support and in this case, keep it always to WPF ResourceDictionary so that regardless of the content/resources, you are able to merge it into your runtime ResourceDictionaries - this means, your external sources will need to provide the WinForms control wrappers for you.

As someone who has created a plugin framework before using this pattern, working with a conceptually "pure MVVM" implementation simplifies things considerably - external sources supply a ViewModel class and a ResourceDictionary of the resources for the VM and you let WPF do the heavy-lifting of View determination for you.

Upvotes: 2

silverfighter
silverfighter

Reputation: 6882

this is a very interesting scenario and for these cases I usually introduce a ViewResolverService or a ViewModelResolverService (or both). So something that can either give you the ViewModel based on a view (class,type or name) match them to host them in the ContentControl. Or a Service which can give you a view based on the ViewModel (type, or string name). With this powerful concept you can use ContentControls and/or DataTemplates and you have full control.

I answered some questions explaining the concepts here:

Register all viewmodel and services in ViewModelLocator

and here: Get the View & ViewModel from a plugin

more here: https://stackoverflow.com/search?q=ViewModelResolver

So if you look at it from the birds eye view you need to apply MVVM to your ContentControls with your views. (And the views have also MVVM applied within themselves).

HTH

Upvotes: 2

Kylo Ren
Kylo Ren

Reputation: 8813

Use DataTemplate for ContentControls:

    <DataTemplate DataType="{x:Type vm:DataSourceViewModel}">
           <view:DataSourceView></view:DataSourceView>
      </DataTemplate>
      <DataTemplate DataType="{x:Type vm:SelectTemplateViewModel}">
           <view:SelectTemplateView></view:SelectTemplateView>
      </DataTemplate>
.........
........
    <ContentControl Margin="5"  HorizontalAlignment="Stretch" Content="{Binding CurrentPage}" Name="ImportControls"></ContentControl>

VM:is the object type that is content of your contentcontrol

View: is specific view you want to see if object of a specific type is set as content of ContentControl

Upvotes: 0

Related Questions