Search code examples
wpfvb.netdatatemplatedatatrigger

Where are Setters in DataTemplate.Triggers applied when no TargetName is given?


In my scenario, I have an ItemsControl where the DataTemplate needs to change in a few ways depending on the properties of the item. One of the things that needs to change is the context menu for the item.

I've put together this example to show my situation. Take the following classes:

Class MainWindow
    Public Property Things As New List(Of Thing) From {New Thing With {.Name = "Thing 1", .Type = ThingType.A},
                                                       New Thing With {.Name = "Thing 2", .Type = ThingType.B},
                                                       New Thing With {.Name = "Thing 3", .Type = ThingType.C}}
End Class

Public Class Thing
    Public Property Name As String
    Public Property Type As ThingType
End Class

Public Enum ThingType
    A
    B
    C
End Enum

Thing has some data to display (Name), and something that should influence the template (Type).

Now take the following XAML:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:VBTest"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <Window.Resources>
        <ContextMenu x:Key="DefaultMenu">
            <MenuItem Header="A or C"/>
        </ContextMenu>
        <ContextMenu x:Key="BMenu">
            <MenuItem Header="B Only"/>
        </ContextMenu>
    </Window.Resources>

    <ItemsControl ItemsSource="{Binding Things}">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="local:Thing">
                <TextBlock Name="Title" Text="{Binding Name}"/>

                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding Type}" Value="B">
                        <Setter TargetName="Title" Property="FontWeight" Value="Bold"/>
                        <Setter Property="ContextMenu" Value="{StaticResource BMenu}"/>
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>    
</Window>

In DataTemplate.Triggers, I define a trigger for items of type B. For these items, I want to change some aspects of the template, so I use TargetName on the Setter to change the FontWeight of a TextBlock (in the real code I change a number of properties for a number of elements). But I also want to change the ContextMenu for the entire item. To do this, I use a Setter without TargetName and the property ContextMenu.

The above works somehow. If you run this and right-click "Thing 2" you see BMenu. But how does this work? What object is the Setter being applied to? DataTemplate has no ContextMenu property.

And I ask this for more than just curiosity. I want all other types of items (A and C) to use DefaultMenu. That should be the default, with the DataTrigger overriding it for B items. But I don't even know what object I'm Setter-ing at this point, so I don't know where or how to set this default.


Solution

  • What object is the Setter being applied to?

    It's applied to the ContentPresenter item container that wraps each element in the Things source collection.

    You can set the default ContextMenu using an ItemContainerStyle:

    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="ContextMenu" Value="{StaticResource DefaultMenu}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>