Sypher_04
Sypher_04

Reputation: 121

WPF Creating different ListBox row templates based on a bound value

I do not have any code at the moment to share. Just a design question.

I have a class that defines a label, and an associated entry type, which I would like to bind to a ListBox. If the type is for example, "Postal Code", I need the ListBox to create the row as a TextBlock and a TextBox. For "Yes/no", I need it to know instead to create a TextBlock with a CheckBox beside it. There will likely be 7 or 8 of these different row types.

What is the best way to approach this?

Upvotes: 1

Views: 1391

Answers (3)

Steven Rands
Steven Rands

Reputation: 5421

The best way to approach this would be to have a collection property containing all of the items that you want to see in your ListBox, bind that collection to a control that displays lists of items, and use different data templates to change the visuals used for each type of item.

For example, you might have a postal code type:

public class PostalCodeEntry
{
    public string Value { get; set; }  // Implement using standard INotifyPropertyChanged pattern
}

And a "Boolean" type:

public class BooleanEntry
{
    public bool Value { get; set; }  // Implement using standard INotifyPropertyChanged pattern
}

You said you wanted a label for each entry type, so a base class would be a good idea:

public abstract class EntryBase
{
    public string Label { get; set; }  // Implement using standard INotifyPropertyChanged pattern
}

Then BooleanEntry and PostalCodeEntry would derive from EntryBase.

That's the Models sorted. You just need a collection of these so that you can bind to them from the UI. Add an appropriate collection property to your Window or ViewModel:

public ObservableCollection<EntryBase> Entries { get; private set; }

In your UI (the View, implemented in XAML), use an instance of a control that knows how to bind to a list of items and visualize them. In WPF this would be an ItemsControl or a control that derives from it such as ListBox or ListView:

<ItemsControl ItemsSource="{Binding Entries}" />

You can see how we bind the ItemsSource property to our code-behind property named Entries. The ItemsControl (and its descendants) knows how to convert those items into a visual representation. By default, your custom object (EntryBase in our example) will be converted into a string and displayed as a text block. However, by using data templates you can control how the conversion from object to visual happens.

If you add a couple of data templates to the resources section like so:

<Window ... xmlns:my="clr-namespace:---your namespace here---">
    <Window.Resources>
        <DataTemplate DataType="{x:Type my:PostalCodeEntry}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Label}" />
                <TextBox Text="{Binding Value}" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type my:BooleanEntry}">
            <CheckBox Content="{Binding Label}" IsChecked="{Binding Value}" />
        </DataTemplate>
    </Window.Resources>

Then add the <ItemsControl ... element after that, then you should see a TextBlock/TextBox combo for PostalCodeEntry types and a CheckBox for BooleanEntry types.

Hopefully if you can get this working it will give you an idea how you could extend it to cope with other model types and their matching data templates.

Upvotes: 1

Gordon True
Gordon True

Reputation: 963

Have a look at the ItemTemplateSelector Property. This property allows you to provide custom logic for choosing which template to use for each item in a collection.

First define your various templates in a resource dictionary...

<Application>
  <Application.Resources>
    <DataTemplate x:Key="TextBoxTemplate">
      <!-- template here -->
    </DataTemplate>
    <DataTemplate x:Key="CheckBoxTemplate">
      <!-- template here -->
    </DataTemplate>
  </Application.Resources>
</Application>

Then, create a custom DataTemplateSelector...

public class MyTemplateSelector : DataTemplateSelector
{
  public override DataTemplate SelectTemplate(object item, DependencyObject container)
  {
    var myObj= item as MyObject;
    if (myObj != null) 
    {
      if (myObj.MyType is PostalCode)
      {
         return Application.Resources["TextBoxTemplate"] as DataTemplate;
      }
      if (myObj.MyType  is YesNo)
      {
        return Application.Resources["CheckBoxTemplate"] as DataTemplate;
      }
    }

    return null; 
  }
}

Then, its just a matter of using the ItemTemplateSelector property...

<Window>
  <Window.Resources>
    <local:MyTemplateSelector x:Key="tempSelector" />
  </Window.Resources>
  <ListBox ItemSource="{Binding items}" ItemTemplateSelector="{StaticResource tempSelector}" />
</Window>

Upvotes: 2

dymanoid
dymanoid

Reputation: 15197

You can use the DataTrigger class.

A DataTrigger allows you to set property values when the property value of the data object matches a specified Value.

Alternatively, you can use the DataTemplateSelector class.

Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object.

Upvotes: 2

Related Questions