Jan 14, 2006

How to display items in an ItemsControl using different templates

13TemplatingItems

I will show you two ways to display some items of a data bound collection differently from others. The rule of thumb is straightforward: if you want to differentiate items that are of the same type based on one of their properties, you should use DataTemplateSelector; if your data items are of different types and you want to use the types to differentiate them, then using implicit data templating is a simpler way to do this.

Let us consider the scenario where the source collection has elements that are all of the same type. In this case, the goal is to change the way they are displayed based on some property in the data element, and using a DataTemplateSelector is the way to go. In the sample code below, the ListBox is bound to a collection of Places, where Place is an object with properties Name and State. I want places in Washington state to be displayed differently from other places, so I defined two DataTemplates in the resources. Then I wrote a PlaceTemplateSelector that picks the correct DataTemplate based on the State property of a Place. Finally, I instantiated a ListBox whose ItemTemplateSelector DependencyProperty is set to the selector I defined.

    <Window.Resources>
        <local:Places x:Key="places" />

        <DataTemplate x:Key="washingtonTemplate">
            <Border Background="Lavender">
                <TextBlock Text="{Binding Path=Name}" Foreground="CornFlowerBlue" FontWeight="Bold"/>
            </Border>
        </DataTemplate>

        <DataTemplate x:Key="notWashingtonTemplate">
            <TextBlock Text="{Binding Path=Name}" Foreground="DarkSeaGreen" />
        </DataTemplate>

        <local:PlaceTemplateSelector WashingtonTemplate="{StaticResource washingtonTemplate}" NotWashingtonTemplate="{StaticResource notWashingtonTemplate}" x:Key="placeTemplateSelector" />
    </Window.Resources>

    <ListBox ItemsSource="{Binding Source={StaticResource places}}" ItemTemplateSelector="{StaticResource placeTemplateSelector}" Margin="10"/>

Here is the code for the PlaceTemplateSelector:

    public class PlaceTemplateSelector : DataTemplateSelector
    {
        private DataTemplate washingtonTemplate;

        public DataTemplate WashingtonTemplate
        {
            get { return washingtonTemplate; }
            set { washingtonTemplate = value; }
        }

        private DataTemplate notWashingtonTemplate;

        public DataTemplate NotWashingtonTemplate
        {
            get { return notWashingtonTemplate; }
            set { notWashingtonTemplate = value; }
        }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            Place place = (Place)item;

            if (place.State == "WA")
            {
                return washingtonTemplate;
            }
            else
            {
                return notWashingtonTemplate;
            }
        }
    }

Consider now the scenario where the collection has objects with different types added to it. In this case, the goal is to template items differently depending on their type. In the sample code below, the ListBox is bound to a heterogeneous collection that contains both GreekGod and GreekHero objects.

    <Window.Resources>
        <local:GreekGodsAndHeros x:Key="godsAndHeros" />
    </Window.Resources>

    <ListBox ItemsSource="{Binding Source={StaticResource godsAndHeros}}" Margin="10"/>

Sure, a DataTemplateSelector could be used to template the items by picking the correct DataTemplate depending on the type of the item passed to the SelectTemplate method, as I have seen a few people do. However, implicit data templating is a better way to do this because it accomplishes the same thing all in xaml (no need for code behind). To use a DataTemplate implicitly, instead of setting its key (with x:Key), I set the DataType property to the type I want it to be applied to.

    <DataTemplate DataType="{x:Type local:GreekGod}">
        <Grid>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <RowDefinition Height="Auto"/>
            <TextBlock Text="{Binding Path=GodName}" Grid.Column="0" Grid.Row="0" Foreground="Brown"/>
            <TextBlock Text="{Binding Path=GodDescription}" Grid.Column="1" Grid.Row="0" Foreground="Brown"/>
        </Grid>
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:GreekHero}">
        <TextBlock Text="{Binding Path=HeroName}" FontWeight="Bold" Foreground="Red"/>
    </DataTemplate>

Here is a screen shot of the completed sample:

Here you can find the VS project with this sample code. This works with December CTP WPF bits.

15 Comments
  1. Marc

    Hallo Beatriz,

    I adapted the God/Hero as follows:

    1.- in Window1, declared interface IGreekGod and IGreekHere with corresponding properties
    2.- in Window1, declared GreekGod:IGreekGod (same with Hero)
    3.- in xaml, changed to DataTemplate DataType=”x:Type local:IGreekGod” (same with Hero)

    result: list with ‘TemplatingItems.GreekGod/Hero’

    question: still have to wait for interface support or did I miss something ?

    thanks very much, marc

    • Bea

      We discussed adding support for interfaces a while ago but ended up not implementing it because we could not come up with a good design for it. The problem was that interfaces don’t have a hierarchy like object types do. Consider the scenario where your data source implements both IGreekGod and IGreekHero and you have DataTemplates for both of those interfaces in the resources: which DataTemplate do you think we should pick up?

      When doing implicit data templating for object types, we first try to find a DataTemplate for the exact type, then for its parent, grandparent and so on. There is very well defined order of types for us to apply. When we talked about adding support for interfaces, we considered using reflection to find out all interfaces and adding them to the end of the list of types. The problem we encountered was defining the order of the interfaces when the type implements multiple interfaces.

      The other thing we had to keep in mind is that reflection is not that cheap, and this would decrease our perf a little for this scenario.

      The data team is obviously open to feedback on this, ideas are welcome as always. If we know of a good design for this, we are happy to consider adding this feature for V2.

  2. Brett

    Hi Beatriz,

    Can you do something similar with non-collection based controls? For example, I would like to use a TreeView to navigate a hierarchy in one panel. In the second panel I would like to bind to SelectedItem of the TreeView use a class-specific DataTemplate to display the class fields. Is this possible?

    Obrigado!

    Brett

    • Bea

      Ola Brett,

      Yes, implicit data templating works for Controls that do not expect an IEnumerable too, it is not specific to ItemsControl. You can try that easily by data binding a Button to some source and adding one or more DataTemplates to the resources with DataType set. You will see that if you have a DataTemplate with the data type you’re binding to, that template will be applied.

      The scenario you just described is the Master-Detail scenario. Sure, databinding the details to the selected item in the TreeView will work just fine. I prefer to use CollectionViewSource instead though, because it uses a data concept (current item) instead of a UI concept (selection) to control what data to display. (You can find more details about this in my post on Friday, November 18, 2005.)

      Thanks,
      Bea

      • Bea

        Ola Bea,

        Thanks for the confirmation and for the hint on using CollectionViewSource. I was trying to do a quick prototype and thought I would see how binding to other controls worked.

        My thanks as well for this blog! It is difficult to evaluate the WPF without good examples like those found in your posts.

        Best,

        Brett

  3. Barry

    Hi Beatrize,

    I apologise if this is a little off-topic but you seem to be the most knowledgable person around with regards to data binding and a problem I am having is data binding-related.

    I have posted a question on MSDN’s WPF forum to which I have had no reply. I find this a little worrying because what I want to do should be quite straightforward.

    Would you be kind enough to look at my posting? If so it’s at,

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=247378&SiteID=1

    • Bea

      Hi Barry,

      I’ve replied to your post in the forums. Let me know if I got your scenario right.

      Thanks,
      Bea

  4. Manny

    Hello Beatriz,

    I have modified your examples to try to databind Source values and SourceRect values for a FormatConvertedBitmap
    and a CroppedBitmap (below) in a datatemplate. However, when I do so I get exceptions about not being able to set the Source and SourceRect values. Is it possible to databind those properties?

    FormatConvertedBitmap x:Name=”masterSource” Source=”{Binding Path=Filename}”
    DestinationFormat=”gray4″

    CroppedBitmap Source=”{Binding Path=Filename}” SourceRect=”{Binding Path=CropRext}”

    Thanks,
    Manny

    • Bea

      Manny,

      Currently you can not bind to those properties, although they are DPs.

      Notice that CroppedBitmap implements ISupportInitialize. In its implementation of this interface, it restricts the Source property to be set between calling BeginInit() and EndInit(). An exception is thrown if Source is not set between these calls, or if it’s set outside of these calls. This was done this way to improve our perf. Unfortunately, this a problem for data binding because we don’t have the value ready during this initialization.

      It’s unlikely that this behavior will change for V1, but this scenario will not be forgotten when we discuss work for future versions of Avalon.

      Thanks,
      Bea

      • Sergey Galich

        hey Bea, it’s 2009 already, do you know if there are any chance to use FormatConvertedBitmap with data binding? I tried to work around with value converters, but that doesn’t work either. The only solution I can imagine now is to move toward effects, but this is something i would like to avoid atm.

        • Bea

          Hi Sergey,

          This scenario was never fixed in WPF, unfortunately.

          However, you should be able to work around it with a converter, as you mention. This is how you can do it:

          <Image Source=”{Binding ElementName=tb, Path=Text, Converter={StaticResource imageConverter}}” />

          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
          FormatConvertedBitmap fcb = new FormatConvertedBitmap();
          fcb.BeginInit();
          fcb.Source = new BitmapImage(new Uri((string)value));
          fcb.EndInit();
          return fcb;
          }

          Let me know if this works for you.

          Bea

          • Sergey

            Bea, thanks for the reply, but I have chosen to use shader effect instead of bitmap conversion. My scenario was to gray button’s image if mouse is not over it.

            Sergey

          • Bea

            That works too.

  5. Geoff

    Hi there, I have tried using the DataTemplateSelector but it only seems to work on the initial binding to the collection. When I changed a property the propertychanged event is sent but the datatemplate selector is never hit again. Any ideas?

    • Bea

      Hi Geoff,

      The behavior you describe is by design and it was implemented for performance reasons.
      If you want to have the DataTemplate be re-evaluated, you can add a handler for property changes. In this handler, you add code that determines the new DataTemplate and sets it explicitly.

      Bea

Comments are closed.