Search code examples
c#wpfxamllistbox

WPF Listbox use DisplayMemberPath with ItemTemplate for dynamic property display


I am having difficulty finding a way to use a DataTemplate in my ListBox and still be able to use a DisplayMemberPath which is dynamically assigned (please note this is a requirement). I have read quite a few posts on the subject and all the answers amount to "don't use a DataTemplate" or "specify the DisplayMemberPath statically in the DataTemplate", neither of which is a workable solution for me.

My goal is to build a custom UserControl called MyListBox which will be used in many different contexts, and it contains a ListBox and some other supporting UI items. So my ListBox has its DisplayMemberPath bound to a DependencyProperty of the same name defined in the MyListBox code-behind, so that when MyListBox is used in other windows, the DisplayMemberPath can be assigned based on the needs of that particular window.

This all works fine, but now if I want to use a DataTemplate as well in this ListBox (so that I can customize list items by adding a CheckBox and possibly other items), the DisplayMemberPath assigned in ListBox no longer works, and I cannot find a way to assign a dynamic PropertyPath within the DataTemplate.

MyListBox.xaml

<UserControl x:Class="MyListBox" x:Name="slb">
<Grid>        
    <ListBox x:Name="listBox" 
          DisplayMemberPath="{Binding ElementName=slb, Path=DisplayMemberPath}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">                        
                    <CheckBox/>
                    <TextBlock x:Name="lbTextItem" 
                               Text="{This will overwrite DisplayMemberPath above}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>           
    </ListBox>
</Grid>
</UserControl>

To be used as follows:

MainWindow.xaml

<Window>
    <Grid>
        <local:MyListBox ItemsSource="{Binding SomeDataSet}"
                         DisplayMemberPath="InterestingProperty"/>
    </Grid>
</Window>

Is there some way around this issue? Is there some way I am missing to dynamically use my DisplayMemberPath dependency property within the DataTemplate of MyListBox? If there was a way for me to access the TextBlock used in the DataTemplate from the code-behind, I could change its PropertyPath in the handler for the DisplayMemberPathPropertyChanged, but I cannot find a reasonably simple way to do that.


Solution

  • Not sure if this is the most simple approach, but you could use a derived ListBox that sets the Content property of its ListBoxItems via a Binding that is created in code.

    Be aware that you can not use the DisplayMemberPath property of the derived ListBox, because the framework throws an exceptipon when both DisplayMemberPath and ItemTemplate are set. So you have to declare another property like shown below.

    public class CustomListBox : ListBox
    {
        public static readonly DependencyProperty BindingPathProperty =
            DependencyProperty.Register(
                nameof(BindingPath), typeof(string), typeof(CustomListBox));
    
        public string BindingPath
        {
            get => (string)GetValue(BindingPathProperty);
            set => SetValue(BindingPathProperty, value);
        }
    
        protected override void PrepareContainerForItemOverride(
            DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
    
            ((ListBoxItem)element).SetBinding(
                ListBoxItem.ContentProperty,
                new Binding(BindingPath) { Source = item });
        }
    }
    

    In your MyListBox, you would use it like this:

    <UserControl x:Class="MyListBox" x:Name="slb">
    <Grid>        
        <local:CustomListBox x:Name="listBox" 
            BindingPath="{Binding DisplayMemberPath, ElementName=slb}">
            <local:CustomListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">                        
                        <CheckBox/>
                        <TextBlock Text="{Binding}"/>
                    </StackPanel>
                </DataTemplate>
            </local:CustomListBox.ItemTemplate>           
        </local:CustomListBox>
    </Grid>
    </UserControl>