2
votes

`I have a listbox that contains items that represent results from various tasks. They each extend a common base class "ResultsViewModel" so share certain attributes that the displayer needs.

What I want to define in a resource dictionary, is a datatemplate for each different type of task result, eg Task A will have an implementing class of ResultsAViewModel, Task B will have ResultsBViewModel etc. I want to define a data template for each of these sub classes, bind the ItemsSource of the Listbox to an ObservableCollection (parent class) and using polymorphism for WPF to work out which data template to use at run time. The complication is that depending on various triggers, it will be one of three data templates selected for EACH result type, depending on whether the result is processing, completed or failed. So each derived class will have there templates.

So far, I have applied a general style to the style of the listbox as below

<ListBox Background="{StaticResource AppBackground}" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Padding="10" Style="{StaticResource ResultsItemTemplate}" ItemsSource="{Binding Results}" MouseDoubleClick="ListBox_MouseDoubleClick" />

That style is as follows

    <!-- Region General Results styles -->
<Style TargetType="{x:Type ListBox}" x:Key="ResultsItemTemplate" >
    <Setter Property="Background">
        <Setter.Value>
            Tan
        </Setter.Value>
    </Setter>
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <ContentControl Content="{Binding}">
                    <ContentControl.Style>
                        <Style TargetType="{x:Type ContentControl}">
                            <Setter Property="ContentTemplate" Value="{DynamicResource CalculatingResultsTemplate}"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Path=ProcessingResult}" Value="1">
                                    <Setter Property="ContentTemplate" Value="{DynamicResource ProcessedResultsTemplate}"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Path=ProcessingResult}" Value="-1">
                                    <Setter Property="ContentTemplate" Value="{DynamicResource ErroredResultsTemplate}"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
                                    <Setter Property="Background" Value="White"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ContentControl.Style>
                </ContentControl>
            </DataTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="Background" Value="{StaticResource AppBackground}" />
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True" >
                        <Setter Property="Background" Value="{StaticResource AppBackground}" />
                    </Trigger>
                </Style.Triggers>
                <Style.Resources>
                    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
                    <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
                </Style.Resources>
            </Style>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapPanel />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
</Style>

<!-- EndRegion -->

Importantly, you can see that this style sets the ItemTemplate to one of three values depending on triggers. These template keys are

ProcessedResultsTemplate CalculatingResultsTemplate ErroredResultsTemplate

Each of which is a data template in my resource dictionary.

The problem I have is that I need one of each of the above three datatemplates for EACH derived viewmodel type. And I am referencing them by key. And you cannot have two items in your dictionary with the same key. If I create two datatemplates with the key "ProcessedResultsTemplate" for example, one with a DataType of x:Type CalculationResultsViewModel and a second one with the same key but DataType of x:Type SpotStressResultsViewModel, then it wont work as both have the same key.

So I'm not sure exactly the right way to implement this to get some WPF polymorphism fun happening here. Is it something basic I'm doing wrong?

Update: I've tried going down the datatemplate selector and using the below class, the logic is implemented fine, but the problem is the datatemplateselector is only invoked when the object is first rendered. The original XAML had triggers to change the datatemplate when certain dependency properties changed. How can I use triggers or dependency properties to ask the datatemplateselector to re-choose the datatemplate from the new data values?

class ResultsDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate CalculateOnlyProcessedTemplate { get; set; }
    public DataTemplate CalculateOnlyCalculatingTemplate { get; set; }
    public DataTemplate CalculateOnlyErroredTemplate { get; set; }

    public DataTemplate SpotStressProcessedTemplate { get; set; }
    public DataTemplate SpotStressCalculatingTemplate { get; set; }
    public DataTemplate SpotStressErroredTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is CalculationResultsViewModel)
        {
            var vm = item as CalculationResultsViewModel;

            if (vm.ProcessingResult == 1)
                return CalculateOnlyProcessedTemplate;

            if (vm.ProcessingResult == -1)
                return CalculateOnlyErroredTemplate;

            return CalculateOnlyCalculatingTemplate;
        }

        if (item is SpotStressResultsViewModel)
        {
            var vm = item as SpotStressResultsViewModel;

            if (vm.ProcessingResult == 1)
                return SpotStressProcessedTemplate;

            if (vm.ProcessingResult == -1)
                return SpotStressErroredTemplate;

            return SpotStressCalculatingTemplate;
        }

        return null;
    }
}
1
Dictionary of Dictionaries?Tony Hopkinson

1 Answers

4
votes

The obvious solution would be to use a DataTemplateSelector for arbitrarily complex decisions of selecting the right template. Shifting actual logic into your XAML will lead to pain.

Personally I think ViewModels should be direct, testable and lookless representations of the views, their composition needs and their interactions. The ViewModel you are demonstrating seem to be more of a business model object instead of representing a view. Your ViewModel layer should be responsible of generating the implied:

  • ResultsACalculatingViewModel
  • ResultsAProcessingViewModel
  • ResultsAErrorViewModel
  • ResultsBCalculatingViewModel
  • ResultsBProcessingViewModel
  • ResultsBErrorViewModel

And your view code should just be styling those. Polymorphism is a nice way of realizing these implied ViewModels easier, but should not be a concern for whoever creates the UI.