Search code examples
wpfxamluser-controlsdatatemplate

How can I set the binding for a control's property (which is inside DataTemplate and UserControl) to use the ItemSource's given property?


I would like to make a UserControl which have a DataTemplate, and inside that DataTemplate there are controls. I would like to bind to those nested (inside the DataTemplate) controls' properties so I can set them when I reuse this UserControl. The nested controls will use the ItemSource's properties but the property names of the ItemSource's properties could be different.

The UserControl:

<UserControl x:Class="ContextMenu.BaseFilterUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             x:Name="Self">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="70" />
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="0"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Right"
                   Margin="10"
                   Text="Owners" />
        <Button Grid.Column="1"
                VerticalAlignment="Center"
                HorizontalAlignment="Center"
                Margin="10"
                Click="FilteButtonClicked"
                Width="40"
                Height="40"
                x:Name="FilterButton">
            <Popup x:Name="FilterBoxPopup"
                   PlacementTarget="{Binding ElementName=FilterButton}"
                   Placement="Bottom"
                   StaysOpen="False">
                <Border BorderBrush="Black"
                        Background="White"
                        Margin="2">
                    <ListView ItemsSource="{Binding ElementName=Self, Path=FilterList}"
                              x:Name="FilterListView"
                              Height="300"
                              Width="150">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <!--<CheckBox IsChecked="{Binding IsChecked}" />-->
                                    <!--<TextBlock Text="{Binding Name}" />-->
                                    <!--This is where I don't know how to properly bind eg. the above control, things I tried:-->
                                    <!--<TextBlock Text="{Binding ElementName=FilterListView, Path=FilterElementName}" />-->
                                    <!--<TextBlock Text="{Binding ElementName=Self, Path=DataContext.FilterElementName}" />-->
                                    <!--<TextBlock Text="{Binding ElementName=FilterListView, Path=DataContext.FilterElementName}" />-->
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </Border>
            </Popup>
        </Button>
        <TextBlock Grid.Column="3"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Left"
                   Margin="10"
                   Text="{Binding ElementName=Self, Path=SelectedNames}" />
    </Grid>
</UserControl>

This how the UserControl is used, the FilterElementName="Name" is what I would like to set, depending on the list binded with FilterList list:

<local:BaseFilterUserControl FilterList="{Binding Owners}"
                             FilterElementName="Name"
                             SelectedNames="{Binding SelectedNames}"/>

In this case the Owners is a simple IReadOnlyList of an Owner class. The Owner class has a string Name property. But I will use this UserControl again with different list eg. where I would like to use the Versions list's Release property (for the TextBlock inside the UserControl):

<local:BaseFilterUserControl FilterList="{Binding Versions}"
                             FilterElementName="Release"
                             SelectedNames="{Binding SelectedReleases}"/>

The ListView is properly populated with items, so the FilterList DependencyProperty is working. But the nested controls are only working when I hard code the bindings:

<TextBlock Text="{Binding Name}" />

Solution

  • For this to work you would need to bind the Path-property of your TextBlocks Text-Binding to the FilterElementName property of your UserControl. Unfortunately the Path property of the Binding class is not a DependencyProperty and therefore not bindable.

    One way to to achieve your goal would be to use the DisplayMemberPath property of the ListView, which is bindable:

       <ListView x:Name="FilterListView"
                  Width="150"
                  Height="300"
                  ItemsSource="{Binding ElementName=Self, Path=FilterList}"
                  DisplayMemberPath="{Binding ElementName=self, Path=FilterElementName}"/>
    

    If this approach does not work because you need to specify a more complex ItemTemplate, another way would be create a property of type DataTemplate in your UserControl, use that as ItemTemplate in the ListView and specify it from outside like so:

        <local:BaseFilterUserControl FilterList="{Binding Versions}"
                                     SelectedNames="{Binding SelectedReleases}">
            <local:BaseFilterUserControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Release}" />
                </DataTemplate>
            </local:BaseFilterUserControl.ItemTemplate>
        </local:BaseFilterUserControl>