Question

I have two HeaderedContentControls like those below that each have their content property bound to one of two view model properties of the same base type (one control is on the left side of the window and one on the right, thus the view model property names).

However, either view model property can be one of four different derived types. So the left could be an Airplane and the right can be a Car. Then later, the left could be a Boat and right could be an Airplane. I would like the Style property of the header controls to be dynamic based on the derived type. What's the best way to do this declaratively?

<Window...>
    <StackPanel 
        Grid.Row="2"
        Orientation="Horizontal" VerticalAlignment="Top">
        <Border 
            Height="380" 
            Width="330"
            Margin="0,0,4,0"
            Style="{StaticResource MainBorderStyle}">
            <HeaderedContentControl
                Content="{Binding Path=LeftChild}"
                Header="{Binding LeftChild.DisplayName}"
                Style="{StaticResource StandardHeaderStyle}"
            />
        </Border>

        <Border 
            Height="380" 
            Width="330"
            Style="{StaticResource MainBorderStyle}">
            <HeaderedContentControl
                Content="{Binding Path=RightChild}"
                Header="{Binding RightChild.DisplayName}"
                Style="{StaticResource StandardHeaderStyle}"
            />  
        </Border>
    </StackPanel>
</Window>


<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:myViewModelNamespace;assembly=myViewModelAssembly"
    xmlns:vw="clr-namespace:myViewNamespace" >

    <!--***** Item Data Templates ****-->
    <DataTemplate DataType="{x:Type vm:CarViewModel}">
        <vw:CarView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:BoatViewModel}">
        <vw:BoatView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:AirplaneViewModel}">
        <vw:AirplaneView />
    </DataTemplate>

    <!--***** 
        Other stuff including the StandardHeaderStyle and the MainBorderStyle
    ****-->

</ResourceDictionary>
Was it helpful?

Solution

My answer is an elaboration on Archimed's. Don't hesitate to ask further!

<Window x:Class="Datatemplate_selector.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:Datatemplate_selector">
<Window.Resources>
    <DataTemplate DataType="{x:Type local:CarDetail}">
     <Border BorderBrush="Yellow" BorderThickness="2">
        <HeaderedContentControl Margin="4" Foreground="Red">
            <HeaderedContentControl.Header>
                <Border BorderBrush="Aquamarine" BorderThickness="3">
                    <TextBlock Text="{Binding Name}"/>
                </Border>
             </HeaderedContentControl.Header>
            <HeaderedContentControl.Content>
                <Border BorderBrush="CadetBlue" BorderThickness="1">
                    <TextBlock TextWrapping="Wrap" Text="{Binding Description}"/>
                </Border>
            </HeaderedContentControl.Content>
        </HeaderedContentControl>
       </Border>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:HouseDetail}">
        <HeaderedContentControl Margin="4" Foreground="Yellow" FontSize="20"
                    Header="{Binding Name}">
            <HeaderedContentControl.Content>
                <TextBlock Foreground="BurlyWood"  TextWrapping="Wrap"
                           Text="{Binding Description}"/>
            </HeaderedContentControl.Content>
        </HeaderedContentControl>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:ItemDetail}">
        <HeaderedContentControl Margin="4" Foreground="Green" FontStyle="Italic" 
                    Content="{Binding Description}"
                    Header="{Binding Name}">
        </HeaderedContentControl>
    </DataTemplate>
</Window.Resources>
<StackPanel>
    <ItemsControl ItemsSource="{Binding ItemDetails}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="2"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</StackPanel>

using System.Collections.ObjectModel;
using System.Windows;

namespace Datatemplate_selector
{
   public partial class Window1 : Window
    {
        public ObservableCollection<ItemDetail> ItemDetails { get; set; }

        public Window1()
        {
            ItemDetails = new ObservableCollection<ItemDetail>
                              {
                                  new CarDetail{Name="Trabant"},
                                  new HouseDetail{Name="Taj Mahal"}
                              };
            DataContext = this;
            InitializeComponent();
        }
    }

    public class ItemDetail:DependencyObject
    {
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name",
            typeof(string),
            typeof(ItemDetail),
            new UIPropertyMetadata(string.Empty));

        public virtual string Description
        {
             get { return Name + " has a lot of details"; }
        }
    }

    public class CarDetail:ItemDetail
    {
        public override string Description
        {
            get { return string.Format("The car {0} has two doors and a max speed of 90 kms/hr", Name); }
        }
    }

    public class HouseDetail:ItemDetail
    {
        public override string Description
        {
            get { return string.Format("The house {0} has two doors and a backyard", Name); }
        }
    }
}

PS: I thought that this use of inheritance in a generic collection was not supported in .Net 3. I am pleasurably surprised that this code works!

OTHER TIPS

Are you sure you need to vary HeaderedContentControl's Style, not the ContentTemplate basing on Content's dynamic type? In other words: do you need to vary the control's style or you just need to vary the item's data-template?

Because there is very handy property ContentTemplateSelector and if you'll write very simple class you'll be able to select the DataTemplate basing on content's dynamic type.

If that's not the case and you are sure you need to vary the Style, then could you please elaborate a little which parts of the style you'd like to vary - maybe there a workaround through the same ContentTemplateSelector is available.

In case you insist on varying the styles, think a little about using data trigger inside your style - using a very simple converter you'll be able to vary certain properties (or all of them if you prefer) of your style.

I'll be glad to provide you further assistance as soon as you'll elaborate the specifics of your problem.

UPD: OK, author insists that he need to vary the Style. Here are two possible ways of how you can do that.

First and simple solution, but severely limited one: since your Header content can be specified through Content content you can do this:

<DataTemplate x:Key="DefaultTemplate">
    <HeaderedContentControl Content="{Binding}"
                            Header="{Binding DisplayName}"
                            Style="{StaticResource DefaultStyle}" />
</DataTemplate>
<DataTemplate x:Key="CarTemplate"
              DataType="dm:Car">
    <HeaderedContentControl Content="{Binding}"
                            Header="{Binding DisplayName}"
                            Style="{StaticResource CarStyle}" />
</DataTemplate>
<DataTemplate x:Key="BoatTemplate"
              DataType="dm:Boat">
    <HeaderedContentControl Content="{Binding}"
                            Header="{Binding DisplayName}"
                            Style="{StaticResource BoatStyle}" />
</DataTemplate>

<u:TypeBasedDataTemplateSelector x:Key="MySelector"
                                 DefaultTemplate="{StaticResource DefaultTemplate}"
                                 NullTemplate="{StaticResource DefaultTemplate}">
    <u:TypeMapping Type="dm:Car" Template="{StaticResource CarTemplate}" />
    <u:TypeMapping Type="dm:Boat" Template="{StaticResource BoatTemplate}" />
</u:TypeBasedDataTemplateSelector>

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

The only code you'll need to back this purely declarative solution is a very simple template selector implementation. Here it goes:

public class TypeMapping
{
    public Type Type { get; set; }
    public DataTemplate Template { get; set; }
}

public class TypeBasedDataTemplateSelector : DataTemplateSelector, IAddChild
{
    public DataTemplate DefaultTemplate { get; set; }
    public DataTemplate NullTemplate { get; set; }
    private readonly Dictionary<Type, DataTemplate> Mapping = new Dictionary<Type, DataTemplate>();

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item == null)
            return NullTemplate;

        DataTemplate template;

        if (!Mapping.TryGetValue(item.GetType(), out template))
            template = DefaultTemplate;

        return template;
    }


    #region IAddChild Members

    public void AddChild(object value)
    {
        if (!(value is TypeMapping))
            throw new Exception("...");

        var tm = (TypeMapping)value;

        Mapping.Add(tm.Type, tm.Template);
    }

    public void AddText(string text)
    {
        throw new NotImplementedException();
    }

    #endregion
}

The second solution is more generic and can be applied to the cases where Header content has nothing to do with Content content. It bases on the Binding's converter capabilities.

<Style x:Key="StandardHeaderedStyle">
    <!--...-->
</Style>

<Style x:Key="CarHeaderedStyle"
       BasedOn="{StaticResource StandardHeaderedStyle}">
    <!--...-->
</Style>

<Style x:Key="BoatHeaderedStyle"
       BasedOn="{StaticResource StandardHeaderedStyle}">
    <!--...-->
</Style>

<Style x:Key="UnknownHeaderedStyle"
       BasedOn="{StaticResource StandardHeaderedStyle}">
    <!--...-->
</Style>

<u:StylesMap x:Key="MyStylesMap" 
             FallbackStyle="{StaticResource UnknownHeaderedStyle}">
    <u:StyleMapping Type="Car" Style="{StaticResource CarHeaderedStyle}" />
    <u:StyleMapping Type="Boat" Style="{StaticResource BoatHeaderedStyle}" />
</u:StylesMap>

<u:StyleSelectorConverter x:Key="StyleSelectorConverter" />

<HeaderedContentControl Content="{Binding LeftChild}"
                        Header="{Binding LeftChild.DisplayName}">
    <HeaderedContentControl.Style>
        <Binding Path="LeftChild"
                 Converter="{StaticResource StyleSelectorConverter}"
                 ConverterParameter="{StaticResource MyStylesMap}" />
    </HeaderedContentControl.Style>
</HeaderedContentControl>

It also requires some of backing code:

public class StyleMapping
{
    public Type Type { get; set; }
    public Style Style { get; set; }
}

public class StylesMap : Dictionary<Type, Style>, IAddChild
{
    public Style FallbackStyle { get; set; }

    #region IAddChild Members

    public void AddChild(object value)
    {
        if (!(value is StyleMapping))
            throw new InvalidOperationException("...");

        var m = (StyleMapping)value;

        this.Add(m.Type, m.Style);
    }

    public void AddText(string text)
    {
        throw new NotImplementedException();
    }

    #endregion
}

public class StyleSelectorConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var m = (StylesMap)parameter;

        if (value == null)
            return m.FallbackStyle;

        Style style;
        if (!m.TryGetValue(value.GetType(), out style))
            style = m.FallbackStyle;

        return style;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

HTH

try using the Style Selector class:

http://msdn.microsoft.com/en-us/library/system.windows.controls.styleselector.aspx

I haven't used it myself specifically, so i don't have any sample code for you to check out, but the MSDN link has some.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top