PanJanek
PanJanek

Reputation: 6685

How to style different items differently when using data template in xaml?

I would like my elements generated from datatemplate to look differently depending on some properties in the model. For example I want to display the text in various colors or present different images or path drawing for each generatred element. I know how to do it in general, but I'm looking for a solution that would allow editing the visual details in Expression Blend by designer without touching the code. For example the simplest solution:

   <DataTemplate>
        <StackPanel Orientation="Horizontal"> 
            <Image Source="{Binding MyImageSource}"/>
            <TextBlock Width="200"  Text="{Binding MyText}" Forecolor="{Binding MyColor}"></TextBox> 
        </StackPanel> 
    </DataTemplate> 

Where 'MyImageSource' and 'MyColor' are properties of the item model (of type ImageSource and Brush) does not satisfy my needs because I don't want to assign these values. I want the designer to do that. Instead of 'MyImageSource' and 'MyColor' properties my model would have a property like 'ItemType' or 'ItemStyle' of type enum or string (or some other type). I'm not looking for "religious" MVVM strict solution. My only requirement is to avoid the need for the designer to wait for me to correct the code following his instructions like "change the item color of type X in list Y to #FFAACC" because it seems like breaking the SoC rule in a way.

EDIT (based on the answers):

I've found similar solution to the one described by bendewey here - it requires to derive custom control for the control using ItemsSource attribute. The idea of using different datatemplates for each element type is neat, but in my opinion it covers the situation when we want to generate completely different visual elements for each item. When the elements differ only in some colors and image (and contain lots of common elements apart from that), then creating separate datatemplate for each element type would cause unnecessary code (xaml) repetition. In this situation Vlad's solution suits better. Is there any other technique apart from theese two?

Upvotes: 3

Views: 2854

Answers (3)

MikNik
MikNik

Reputation: 84

Afaik, DataTemplateSelector is not Blend-compatible. At least I couldn't make it work.

A completely Blendable solution involves one custom control, inherited from a ListBox and exposing a property (one or more, but one is easier), which would regulate, which data templates to apply. Then you edit the style for this ListBox and add triggers. Inside a trigger (Property Trigger) you change the data template to the one matching the trigger value.

Example:

<Style x:Key="OrientedListStyle" TargetType="{x:Type local:OrientedListBox}">
<Setter Property="ItemTemplate" Value="{DynamicResource TemplateVertical}"/>
<Setter Property="ItemsPanel" Value="{DynamicResource PanelVertical}"/>

<Style.Triggers>
    <Trigger Property="Orientation" Value="Horizontal">
        <Setter Property="ItemTemplate" Value="{DynamicResource TemplateHorizontal}"/>
        <Setter Property="ItemsPanel" Value="{DynamicResource PanelHorizontal}"/>
    </Trigger>
</Style.Triggers>

Upvotes: 0

bendewey
bendewey

Reputation: 40265

You can steal the ItemTemplateSelector concept from the WPF playbook. This is what that would look like:

App.xaml

<DataTemplate x:Key="MaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="M - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="FemaleTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="F - " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

MainPage.xaml

<UserControl.Resources>
    <local:PersonTemplateSelector x:Key="PersonTemplateSelector" />
</UserControl.Resources>

<Grid x:Name="LayoutRoot">
    <local:CustomItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource PersonTemplateSelector}" />
</Grid>

MainPage.xaml.cs

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = Person.GetSampleData();
    }
}

PersonTemplateSelector.cs

public class PersonTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var person = item as Person;
        if (person != null)
        {
            switch (person.Gender)
            {
                case Gender.Male:
                    return Application.Current.Resources["MaleTemplate"] as DataTemplate;
                case Gender.Female:
                    return Application.Current.Resources["FemaleTemplate"] as DataTemplate;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        return null;
    }
}

If this is something that makes since and you are interested in using you will need to use this CustomItemsControl, or some variation:

CustomItemsControl.cs

public class CustomItemsControl : ItemsControl
{
    public DataTemplateSelector ItemTemplateSelector
    {
        get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
        set { SetValue(ItemTemplateSelectorProperty, value); }
    }

    public static readonly DependencyProperty ItemTemplateSelectorProperty =
        DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(CustomItemsControl), new PropertyMetadata(null));

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        ContentPresenter presenter = element as ContentPresenter;

        if (presenter != null)
        {
            DataTemplate itemTemplate = null;
            if (base.ItemTemplate != null)
            {
                itemTemplate = base.ItemTemplate;
            }
            else if (this.ItemTemplateSelector != null)
            {
                itemTemplate = this.ItemTemplateSelector.SelectTemplate(item, element);
            }

            presenter.Content = item;
            presenter.ContentTemplate = itemTemplate;
        }
    }
}

public class DataTemplateSelector
{
    public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return null;
    }
}

Upvotes: 6

Vlad
Vlad

Reputation: 35594

You can use converters. Foe example, Forecolor="{Binding MyText, Converter=ColorFromText}. You would need to store color-picking logic into the converter.

Upvotes: 0

Related Questions