wpfxamlcontextmenuitemcontainerstyle

ContextMenu - Strange behavior when using ItemContainerStyle


I'm having a strange issue with my ContextMenu. The ItemsSource is a List<Layer> Layers;, where the Layer class overrides the ToString() to return the Name property.

If I don't use any ItemContainerStyle for the context menu, it all works fine - it takes the Layer object and displays the ToString() of that object, like it should. When I add the ItemContainerStyle, it shows an empty string.

Here's the XAML:

<Style x:Key="myItemControlTemplate" TargetType="{x:Type MenuItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type MenuItem}">
                <Border SnapsToDevicePixels="True" Height="32" Width="200" Background="White">
                    <Grid VerticalAlignment="Center" Height="Auto" Width="Auto" Background="{x:Null}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{TemplateBinding Header}" VerticalAlignment="Top" Foreground="#FF3B3D52"
                                   Grid.Column="1" Margin="6,0,0,0"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


<Button ContextMenuService.IsEnabled="False">
    <Button.ContextMenu>
        <ContextMenu FontFamily="Global Sans Serif" Height="Auto" Width="200" Padding="0,6" VerticalOffset="5" BorderThickness="0" 
                     HasDropShadow="True" ItemContainerStyle="{StaticResource myItemControlTemplate}">
        </ContextMenu>
    </Button.ContextMenu>
</Button>

And here's how I fire it:

private void btn_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;

    ContextMenu ctm = btn.ContextMenu;
    ctm.ItemsSource = Layers;
    ctm.PlacementTarget = btn;
    ctm.Placement = PlacementMode.Bottom;
    ctm.IsOpen = true;
}

Could it be that for some reason this binding gets busted somehow?

Text="{TemplateBinding Header}"

BTW, if I change the layers list to be a List<string> and just feed it the names of the layers, it works correctly with the ItemContainerStyle.

What am I missing?


Solution

  • The issue is the TemplateBinding. This is a less powerful but optimized variant of a relative source binding to a templated parent, but it comes at the expense of several limitations, like not supporting two-way binding. Although not officially stated in the documentation, it seems that this binding does not support conversion of the underlying type to string, as ToString is never called in this case.

    Replace the TemplateBinding with a binding to the templated parent using relative source.

    <TextBlock x:Name="textBlock"
               HorizontalAlignment="Left" TextWrapping="Wrap"
               Text="{Binding Header, RelativeSource={RelativeSource TemplatedParent}}" 
               VerticalAlignment="Top" Foreground="#FF3B3D52"
               Grid.Column="1" Margin="6,0,0,0"/>