Dread Pirate Peter
Dread Pirate Peter

Reputation: 75

Dynamically set UserControl as Listbox DataTemplate body

I have the following setup:

<ListBox ItemSource="{Binding Targets}">
    <ListBox.ItemTemplate>
        <DataTemplate>
          <view:ViewName />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

What I am trying to accomplish is to dynamically decide which view to use at runtime, based on a property within the DataContext of the ListBox. In simple terms, I want to replace <view:ViewName> with a data binding that returns the proper view.

I use MEF to provide plug-ins for my app that may need to provide a custom view to display the items when appropriate. At design time I won't know all the possible view types (they may be dynamically loaded from a DLL) so a simple DataTemplateSelector won't do.

I have researched solutions but have come up empty.

Upvotes: 1

Views: 570

Answers (2)

Rachel
Rachel

Reputation: 132618

Since you want to change templates based on a bound value, you can use a DataTrigger to determine the ContentTemplate of the ListBoxItem

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}"/>
    <Style.Triggers>
        <DataTrigger Property="{Binding SomeProperty}" Value="A">
            <Setter Property="ContentTemplate" Value="{StaticResource TemplateA}"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

I find this better than using a DataTemplateSelector because it gets re-evaluated if the bound property changes, while a DataTemplateSelector does not.

If you want to change templates based on an object type, you can use Implicit DataTemplates. These are DataTemplates that define a DataType, but no x:Key, and they will be used anytime WPF tries to draw an object of the specified type.

For example, if you had this template defined in your <X.Resources> somewhere

<DataTemplate DataType="{x:Type models:ActionA}">
    <views:ActionAView />
</DataTemplate>

you could then insert your Model object directly into the UI and WPF would draw it using the template you specified

<ContentControl Content="{Binding SomeIActionObject}" />

<ItemsControl ItemsSource="{Binding CollectionOfIActionObjects}" />

Update

You mentioned that you would be allowing users to create modules with additional Templates that get imported using MEF, so in that case you would probably be better off using an IValueConverter that look up the matching template within Application.Resources

For example, if the bound value equals "A", then the converter might search Application.Resources for a template named "TemplateA" and return it to the binding

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="ContentTemplate" 
            Value="{Binding SomeProperty, 
                Converter={StaticResource MyTemplateConverter}}"/>
</Style>

Upvotes: 2

Fede
Fede

Reputation: 44068

Using the DataTemplateManager from this post You can do something like:

DataTemplateManager.RegisterDataTemplate<ViewModelType1, ViewType1>();
DataTemplateManager.RegisterDataTemplate<ViewModelType2, ViewType2>();
DataTemplateManager.RegisterDataTemplate<ViewModelType3, ViewType3>();

then you would remove the ItemTemplate from the ListBox:

<ListBox ItemSource="{Binding Targets}"/>

and in the ListBox ViewModel you could:

public void AddTargets()
{
    Targets.Add(new ViewModelType1());
    Targets.Add(new ViewModelType2());
    Targets.Add(new ViewModelType3());
}

Then, each DataTemplate will be automatically used by WPF to render each corresponding ViewModel.

Also note that you can call DataTemplateManager.RegisterDataTemplate() at any time before showing the ListBox, so you can theoretically do that when loading the MEF parts.

Edit:

Based on your comment, you could create a single DataTemplate with a ContentPresenter to display the selected View according to a property in the ViewModel:

<DataTemplate DataType="{x:Type local:TargetViewModel}">
    <ContentPresenter x:Name="MainContentPresenter" Content="{Binding}" ContentTemplate="{Binding YourProperty, Converter=SomeConverter}"/>

and inside the SomeConverter you should use the same technique as demonstrated in the post, to dynamically generate a DataTemplate.

Upvotes: 1

Related Questions