WPF 上下文菜单数据绑定图标仅在检查时出现

WPF context menu data-bound icon only appears when inspected

TreeView 的资源中,我有一个 HierarchicalDataTemplate 也定义了上下文菜单样式。目的是能够以编程方式定义 MenuItems 将图标名称指定为其 Tag,然后让前端显示正确的图标。

<StackPanel.ContextMenu>
    <ContextMenu ItemsSource="{Binding MenuItems}">
        <ContextMenu.Resources>
            <Style TargetType="{x:Type MenuItem}">
                <Setter Property="Icon">
                    <Setter.Value>
                        <local:StringToIcon IconName="{Binding Tag, RelativeSource={RelativeSource AncestorType=MenuItem}}" />
                    </Setter.Value>
                </Setter>
            </Style>
        </ContextMenu.Resources>
    </ContextMenu>
</StackPanel.ContextMenu>

StringToIcon 是另一个控件,为了测试,它看起来就像这样。它由依赖 属性 IconName.

支持
<UserControl x:Class="MyApp.Components.StringToIcon"
             ...
             Name="StringIconControl">
    <Image DataContext="{Binding ElementName=StringIconControl}">
        <Image.Style>
            <Style TargetType="{x:Type Image}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IconName}" Value="Refresh">
                        <Setter Property="Source" Value="{StaticResource IconRefresh}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>
</UserControl>

IconRefresh 只是一个全局可用的资源:

<BitmapImage x:Shared="False" x:Key="IconRefresh" UriSource="pack://application:,,,/Resources/Icons/refresh.png" />

启动应用程序时,none 的上下文菜单刷新图标出现。它们都是空白的。我收到绑定错误,我认为这是由于上下文菜单不在可视化树中造成的:

Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.MenuItem', AncestorLevel='1''. BindingExpression:Path=Tag; DataItem=null; target element is 'StringToIcon' (Name='StringIconControl'); target property is 'IconName' (type 'String')

但是,当使用 Snoop 检查菜单时,所有绑定和依赖性 属性 都是正确的。以这种方式检查后,检查的刷新图标奇迹般地出现了,就好像它强制绑定重新评估一样。您可以对这个图标的每个实例一个一个地执行此操作,它们就会出现。

如何修复似乎发生在此处的惰性绑定?还是有其他事情在起作用?我看到另一个 post 建议将菜单数据上下文附加到可视化树中 的东西,但我看不到在这种情况下会是什么。

绑定问题的最终解决方案是制作一个新的 class 来保存菜单项详细信息:

public class BindableMenuItem
{
    public BindableMenuItem(string name, ICommand command)
    {
        this.Name = name;
        this.Command = command;
    }
    public string Name { get; set; }
    public ICommand Command { get; set; }
    public string IconName { get; set; }
    public ObservableCollection<BindableMenuItem> Children { get; set; }
}

然后将其绑定到菜单项的样式,如下所示:

<Style TargetType="MenuItem">
    <Setter Property="Header" Value="{Binding Name}" />
    <Setter Property="Command" Value="{Binding Command}" />
    <Setter Property="ItemsSource" Value="{Binding Children}" />
    <Setter Property="Icon">
        <Setter.Value>
            <local:StringToIcon IconName="{Binding IconName}" />
        </Setter.Value>
    </Setter>
</Style>

大概这是可行的,因为绑定不依赖于可视化树中的相对源,而是它有一个具体的模型 class 可以使用。

但是,上述关于多个样式实例的问题仍然存在。我会 post 另一个问题。