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?

ASh
  • 34,632
  • 9
  • 60
  • 82
Matt Winckler
  • 2,223
  • 2
  • 23
  • 27
  • 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. – Cameron MacFarland Mar 24 '09 at 06:47
  • 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! – Matt Winckler Mar 24 '09 at 15:07

3 Answers3

18

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}"/>
MrTelly
  • 14,657
  • 1
  • 48
  • 81
  • 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. – Jonathan Rupp Mar 24 '09 at 00:19
  • Hmm, yep I see you're right, I'm not sure that that is possible, I'll await a better answer – MrTelly Mar 24 '09 at 00:56
  • Jonathan's right; I'd like to be able to bind other types of objects to the same template. Thanks for the try, though! – Matt Winckler Mar 24 '09 at 03:24
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.

amiry jd
  • 27,021
  • 30
  • 116
  • 215
Bryan Anderson
  • 15,969
  • 8
  • 68
  • 83
  • 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. – Matt Winckler Mar 24 '09 at 15:29
  • 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. – Bryan Anderson Mar 24 '09 at 18:54
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?

Jake Ginnivan
  • 2,112
  • 16
  • 20
  • 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. – Matt Winckler Mar 24 '09 at 15:12