11

I have a generic style for a ListBox that overwrites the ItemTemplate to use RadioButtons. It works great, EXCEPT when I set a DisplayMemberPath. Then I just get the .ToString() of the item in the ListBox.

I feel like I'm missing something simple here... can someone help me spot it?

<Style x:Key="RadioButtonListBoxStyle" TargetType="{x:Type ListBox}">
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}" >
                <Setter Property="Margin" Value="2, 2, 2, 0" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Border Background="Transparent">
                                <RadioButton
                                    Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
                                    IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>

                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

My ListBox is bound to a List<T> of KeyValuePairs. If I remove the Style, the DisplayMemberPath shows up correctly so it must be something with the style.

<ListBox Style="{StaticResource RadioButtonListBoxStyle}"
         ItemsSource="{Binding MyCollection}"
         DisplayMemberPath="Value" SelectedValuePath="Key" />
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • A little late, but did you try ? Not definining the Content Property as Attribute, but as XElement allows you to use the Contentpresenter-Object, which (in my case on a listbox) returned the Property Value defined in the DisplayMemberpath Property. Not tested, just an idea – dba Jun 27 '16 at 13:52

4 Answers4

11

Why exactly do you want to keep DisplayMemberPath? Its just a shortcut for an ItemTemplate containing a TextBlock showing the value in DisplayMemberPath. With your own ItemTemplate you have much more flexibility what and how you want to display.

Just add a TextBlock into your ItemTemplate and set Text="{Binding Value}" and you have what you want.

As described here:

This property is a simple way to define a default template that describes how to display the data objects.

DisplayMemberPath provides a simple way to a template, but you want a different one. You can't and you don't need both.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
dowhilefor
  • 10,971
  • 3
  • 28
  • 45
  • 3
    Because the Style is used in about 5 different places, so I wanted a generic ItemTemplate style – Rachel Sep 20 '11 at 14:37
  • First of all, i would not override the Style for that, i would provide a DataTemplate for your different data types. Thus you don't need to change the Style just because of your data. The ItemContainerStyle should only contain the look and feel, the DataTemplate should contain how the data is shown. – dowhilefor Sep 20 '11 at 14:42
  • I am overwriting the Style because there are multiple properties at different levels that I want to set, and I didn't want to write out three different bindings to make a ListBox look like a ComboBox. But is your answer that you cannot set `DisplayMemberPath` and `ItemTemplate` at the same time? – Rachel Sep 20 '11 at 14:51
  • I re-read what you said after your edits, and it makes more sense now – Rachel Sep 20 '11 at 15:02
  • 2
    Yes, thats at least what makes sense. Like i said. DisplayMemberPath is imo just syntactic sugar to provider a DataTemplate for your Items, adding a TextBlock and setting the Text binding to the DisplayMemberPath binding. If you supply your own template, this won't work anymore. – dowhilefor Sep 20 '11 at 15:06
5

I still can't figure out how to get it to draw using the DisplayMemberPath, however I did find the piece I was missing to get it drawing using the ItemTemplate - I needed the ContentTemplate binding

<RadioButton 
    Content="{TemplateBinding ContentPresenter.Content}" 
    ContentTemplate="{TemplateBinding ContentPresenter.ContentTemplate}"
    IsChecked="{Binding Path=IsSelected,RelativeSource={
                        RelativeSource TemplatedParent},Mode=TwoWay}" />

Then in my XAML I can write:

<ListBox Style="{StaticResource RadioButtonListBoxStyle}"
         ItemsSource="{Binding MyCollection}"
         SelectedValuePath="Key">

    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Value}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Thanks to dowhilefor for pointing out that DisplayMemberPath is simply a shortcut way of writing the ItemTemplate, and that both cannot be set at once.

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Just a sidenote, you should also set the ContentTemplateSelector, the same way you set Content and ContentTemplate. You are propably don't need it in your case, but it is worth pointing out, that it is another way to supply an item template, and i remember a bug where we forgot exactly that ;) – dowhilefor Sep 20 '11 at 21:53
3

I have been researching this same problem since morning and this thread helped me a lot. I investigated the solution and found out there is a way to support DisplayMemberPath even after defining a custom ListboxItem controltemplate in a style . The key is adding

    ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"

to the contentpresenter( the radiobutton in case of this thread).

The reason this will work is because internally the displaymemberpath property causes the Listbox to assign the ContentTemplateSelector to a "DisplayMemberTemplateSelector" template selector. Without this TemplateBinding in place, this selector does not take effect.

Cheers!

Arjun
  • 268
  • 1
  • 9
0

tl;dr

Set an ItemContainerStyle with a ContentPresenter in it, and make sure not to overwrite the ItemTemplate.

The need

You want to define a generic style to be re-used.

You want to re-use it with different data types, so you want to be able to use DisplayMemberPath or ItemTemplate when re-using - without having to redefine the whole style.

The problem

You were not using a ContentPresenter on your item's container.
The item itself would have nowhere to be drawn.

The solution

For your concrete case

Put a ContentPresenter inside the RadioButton; that should work:

<RadioButton IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
             VerticalAlignment="Center">
    <ContentPresenter />
</RadioButton>

In general

Define the ItemContainerStyle. This lets you control how each item is wrapped.

That Style targets a ListBoxItem.

Define that style's Template, making sure to include a ContentPresenter (where the content of the item itself will be shown).

A sample

Defining the style
<Style TargetType="{x:Type ListBoxItem}" x:Key="BigListBoxItemStyle" BasedOn="{StaticResource DefaultListBoxItemStyle}">
    <Setter Property="Foreground" Value="DeepPink" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Height" Value="71" />
    <Setter Property="FontSize" Value="18" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="BorderBrush" Value="Transparent" />
    <Setter Property="Padding" Value="10 5 10 5" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border Background="{TemplateBinding Background}"
                        Margin="{TemplateBinding Margin}"
                        BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                            <RowDefinition Height="1" />
                        </Grid.RowDefinitions>
                        <ContentPresenter Grid.Row="0" Grid.Column="0"
                                          Margin="{TemplateBinding Padding}"
                                          VerticalAlignment="Center" />
                        <Rectangle x:Name="GraySeparator"
                                   Grid.Row="1"
                                   Height="1" Stroke="Gray" Opacity="0.2"
                                   HorizontalAlignment="Stretch" VerticalAlignment="Bottom" />
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True" >
                        <Setter Property="Background" Value="Yellow" />
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="True" >
                        <Setter Property="BorderBrush" Value="DarkGreen" />
                        <Setter Property="Visibility" Value="Hidden" TargetName="GraySeparator" />
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" Value=".5" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="BigListBoxStyle" TargetType="{x:Type ListBox}">
    <Setter Property="ItemContainerStyle" Value="{StaticResource BigListBoxItemStyle}" />
</Style>
Using it
<ListBox Style="{StaticResource BigListBoxStyle}"
         ItemsSource="{Binding MyTuples}"
         DisplayMemberPath="Item2"
         SelectedItem="{Binding SelectedTuple}">
ANeves
  • 6,219
  • 3
  • 39
  • 63