views:

284

answers:

2

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?

A: 

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

Vlad
Yes, but my main requirement is that the designer could change the Text<->Color correspondence without changing the code, ideally using Expression blend only. I don't know any way to encode converter logic in xaml. Is there any?
PanJanek
I see. Maybe you can have a resource dictionary mapping ItemType into the proper color? <ResourceDictionary> <SolidColorBrush Color="#112233" x:Key="ItemType1"/> ... </ResourceDictionary>Then you can bind to it: <TextBlock Foreground={Binding ItemStyle, Converter=ColorConverter}/>And the converter should take the brush from the dictionary.
Vlad
I will try that, but does blend allow to create and edit detached resource dictionary keys? And what about pictures (images, path drawings) can they be treated as resources and bound inside datatemplate?
PanJanek
It must be possible to edit resource dictionaries in Blend. Moreover, virtually anything can be put into a resource dictionary, including `ImageSource`s and `Brush`es.
Vlad
+4  A: 

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;
    }
}
bendewey
+1 a really useful technique, complete and well presented. Excellent job.
AnthonyWJones
@AnthonyWJones, thanks, I respect your opinion and that means a lot
bendewey
@bendewey: Thanks for the detailed answer. I read about that techniqe some time ago. The drawback of this solution for me is that it's a little too sophisticated. I mean it requires to create custom control for each standard control that use ItemSource to bind to collection. I wolud prefer something that could be used with standard silverlight/wpf controls. Anyway, +1 for providing insightful solution.
PanJanek