WPF MenuItem图标仅显示ContentControl模板单个实例问题
问题描述
我正在通过样式设置上下文菜单中MenuItem的图标,希望根据菜单项关联对象的类型显示不同图标。目前先尝试给所有菜单项添加一个拼图图标,但实际运行时只有最后加载的菜单项显示该图标,打开子菜单时图标还会转移到子菜单最后一项,明显是同一个图标实例被复用了。我明明用了ContentTemplate,为什么会出现这种情况?
相关代码:
<!-- A pizzle-piece icon --> <DataTemplate x:Key="DefaultComponentIconPath"> <Path Data="{StaticResource PuzzlePieceGeometry}" Stroke="{DynamicResource PrimaryBrush}" StrokeThickness="1.8" Stretch="Uniform"/> </DataTemplate> <!-- The style applied to all menu items in the menu --> <Style x:Key="ViewportMenuItemStyle" BasedOn="{StaticResource MenuItemBaseStyle}" TargetType="{x:Type MenuItem}"> <Setter Property="Icon"> <Setter.Value> <ContentControl ContentTemplate="{StaticResource DefaultComponentIconPath}"/> </Setter.Value> </Setter> </Style> <!-- This is needed because there are both MenuItems and Separators in my right-click menu, and if the above style targets a Separator, an error is thrown --> <local:ViewportMenuItemStyleSelector x:Key="ViewportMenuItemStyleSelector" MenuItemStyle="{StaticResource ViewportMenuItemStyle}"/> <!-- The style used for my special right-click menu --> <Style BasedOn="{StaticResource ContextMenuBaseStyle}" TargetType="{x:Type local:ViewportContextMenu}"> <Setter Property="ItemContainerStyleSelector" Value="{StaticResource ViewportMenuItemStyleSelector}"/> </Style>
原因分析
你当前写法的核心问题是:在Style的Setter.Value中直接实例化了一个ContentControl对象。WPF中,Style里直接定义的UI元素是全局共享的单实例——所有应用该Style的MenuItem都会共用这同一个ContentControl实例。而一个UI元素只能属于一个Visual树,所以它会不断被“移动”到最后加载的MenuItem上,最终只在最后一个元素显示,打开子菜单时又会转移到子菜单的最后一项。
你虽然用了ContentTemplate,但模板是作用在这个共享的ContentControl上,并没有改变“单实例复用”的本质。
解决方案
方案1:将ContentControl设为非共享资源
把ContentControl单独定义为资源,并设置x:Shared="False",这样每次引用该资源时都会创建新的实例,避免复用:
<ContentControl x:Key="DefaultComponentIconControl" x:Shared="False" ContentTemplate="{StaticResource DefaultComponentIconPath}"/> <Style x:Key="ViewportMenuItemStyle" BasedOn="{StaticResource MenuItemBaseStyle}" TargetType="{x:Type MenuItem}"> <Setter Property="Icon" Value="{StaticResource DefaultComponentIconControl}"/> </Style>
方案2:直接用DataTemplate生成Icon(更推荐)
跳过额外的ContentControl,直接让MenuItem的Icon通过DataTemplate生成实例。可以修改Style,使用ContentPresenter并绑定模板,或者直接将Path定义为非共享资源:
<!-- 直接定义非共享的Path资源 --> <Path x:Key="DefaultComponentIcon" x:Shared="False" Data="{StaticResource PuzzlePieceGeometry}" Stroke="{DynamicResource PrimaryBrush}" StrokeThickness="1.8" Stretch="Uniform"/> <Style x:Key="ViewportMenuItemStyle" BasedOn="{StaticResource MenuItemBaseStyle}" TargetType="{x:Type MenuItem}"> <Setter Property="Icon" Value="{StaticResource DefaultComponentIcon}"/> </Style>
如果后续需要根据对象类型切换图标,可以结合DataTemplateSelector,为不同类型的菜单项指定不同的图标模板,确保每个MenuItem都能生成自己的图标实例。
内容的提问来源于stack exchange,提问作者Thomas Slade

