How to make a ListBox.ItemTemplate reusable/generic
Asked Answered
A

3

15

I am trying to understand how best to extend the ListBox control. As a learning experience, I wanted to build a ListBox whose ListBoxItems display a CheckBox instead of just text. I got that working in a basic fashion using the ListBox.ItemTemplate, explicitly setting the names of the properties I wanted to databind to. An example is worth a thousand words, so...

I've got a custom object for databinding:

public class MyDataItem {
    public bool Checked { get; set; }
    public string DisplayName { get; set; }

    public MyDataItem(bool isChecked, string displayName) {
        Checked = isChecked;
        DisplayName = displayName;
    }
}

(I build a list of those and set ListBox.ItemsSource to that list.) And my XAML looks like this:

<ListBox Name="listBox1">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding Path=Checked}" Content="{Binding Path=DisplayName}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

This works. But I want to make this template reusable, i.e. I'll want to bind to other objects with properties other than "Checked" and "DisplayName". How can I modify my template such that I could make it a resource, reuse it on multiple ListBox instances, and for each instance, bind IsChecked and Content to arbitrary property names?

Arnuad answered 23/3, 2009 at 23:55 Comment(2)
I don't think this is possible. To have the "Checked" bindinging be flexible you're going to have to subclass the ListBox and add a CheckedMemberPath like the DisplayMemberPath. Can't do this with just a template.Gloam
That's what I was afraid of. I'd started looking into subclassing ListBox, but I kept reading things about how incredible templates are and how you should avoid subclasses if it's at all possible to accomplish what you want with a template. I guess this may be a good case for a subclass. Thanks!Arnuad
D
17

The easiest way is probably to put the DataTemplate as a resource somewhere in your application with a TargetType of MyDataItem like this

<DataTemplate DataType="{x:Type MyDataItem}">
    <CheckBox IsChecked="{Binding Path=Checked}" Content="{Binding Path=DisplayName}" />
</DataTemplate>

You'll probably also have to include an xmlns to your local assembly and reference it through that. Then whenever you use a ListBox (or anything else that uses a MyDataItem in a ContentPresenter or ItemsPresenter) it will use this DataTemplate to display it.

Doze answered 24/3, 2009 at 13:46 Comment(2)
Fascinating! I hadn't realized you could apply the template to the data object itself. (BTW, I found that "TargetType" should actually be "DataType" on a DataTemplate.) The drawback to this, though, is that I would have to define templates for each type of datasource...which I was trying to avoid.Arnuad
Thanks, I've fixed the error. No matter what you'll have to tell the UI which field is IsChecked and which is the Content. You just have to decide where that happens. I tend to prefer it on the DataTemplate level because it has tended to be the easiest place to maintain in my experience.Doze
D
19

Create your DataTemplate as a resource and then reference it using the ItemTemplate property of the ListBox. MSDN has a good example

<Windows.Resources>
  <DataTemplate x:Key="yourTemplate">
    <CheckBox IsChecked="{Binding Path=Checked}" Content="{Binding Path=DisplayName}" />
  </DataTemplate>
...
</Windows.Resources>

...
<ListBox Name="listBox1"
         ItemTemplate="{StaticResource yourTemplate}"/>
Diacaustic answered 24/3, 2009 at 0:7 Comment(3)
I think the questioner is asking how to make 'Checked' and 'DisplayName' parameters that are supplied to the template, so that the same appearance can be used elsewhere, but possibly with a different binding.Maybellemayberry
Hmm, yep I see you're right, I'm not sure that that is possible, I'll await a better answerDiacaustic
Jonathan's right; I'd like to be able to bind other types of objects to the same template. Thanks for the try, though!Arnuad
D
17

The easiest way is probably to put the DataTemplate as a resource somewhere in your application with a TargetType of MyDataItem like this

<DataTemplate DataType="{x:Type MyDataItem}">
    <CheckBox IsChecked="{Binding Path=Checked}" Content="{Binding Path=DisplayName}" />
</DataTemplate>

You'll probably also have to include an xmlns to your local assembly and reference it through that. Then whenever you use a ListBox (or anything else that uses a MyDataItem in a ContentPresenter or ItemsPresenter) it will use this DataTemplate to display it.

Doze answered 24/3, 2009 at 13:46 Comment(2)
Fascinating! I hadn't realized you could apply the template to the data object itself. (BTW, I found that "TargetType" should actually be "DataType" on a DataTemplate.) The drawback to this, though, is that I would have to define templates for each type of datasource...which I was trying to avoid.Arnuad
Thanks, I've fixed the error. No matter what you'll have to tell the UI which field is IsChecked and which is the Content. You just have to decide where that happens. I tend to prefer it on the DataTemplate level because it has tended to be the easiest place to maintain in my experience.Doze
E
2

If you wanted one way display then you could use a converter:

class ListConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((IList<MyDataItem>)value).Select(i => new { Checked = i.Checked2, DisplayName = i.DisplayName2 });
    }

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

Then the xaml would look something like this:

<Window.Resources>
    <this:ListConverter x:Key="ListConverter" />
</Window.Resources>
<ListBox ItemsSource="{Binding Path=Items, Converter={StaticResource ListConverter}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding Path=Checked, Mode=OneWay}" Content="{Binding Path=DisplayName, Mode=OneWay}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

That data template you could make generic like above. Two way binding would be a fair bit more difficult.

I think you are better off making your base classes implement a ICheckedItem interface which exposes the generic properties that you want the datatemplates to bind to?

Ethelynethene answered 24/3, 2009 at 6:5 Comment(1)
Interesting approach--thanks! I'll need two-way binding, so I'll probably avoid the converter route. However, the ICheckedItem opens up new possibilities I hadn't thought of. Interfaces may end up being the way to go.Arnuad

© 2022 - 2024 — McMap. All rights reserved.