2017-08-04 57 views
2

我试图移动一些XAML定义MenuItem风格设置样式附WPPM MenuItem图标

我已经得到了以下工作XAML:

<Menu x:Name="menu" Height="19" Margin="10,10,10.333,0" VerticalAlignment="Top"> 
     <MenuItem Header="_Open"> 
      <MenuItem.Icon> 
       <Viewbox> 
        <ContentControl Content="{DynamicResource appbar.folder.open}" RenderTransformOrigin="0.5,0.5"> 
         <ContentControl.RenderTransform> 
          <TransformGroup> 
           <ScaleTransform ScaleX="2" ScaleY="2"/> 
          </TransformGroup> 
         </ContentControl.RenderTransform> 
        </ContentControl> 
       </Viewbox> 
      </MenuItem.Icon> 
     </MenuItem> 
</Menu> 

在资源看起来是这样的:

<?xml version="1.0" encoding="utf-8"?> 
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      x:Key="appbar.folder.open" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
     <Path Width="44" Height="26" Canvas.Left="19" Canvas.Top="24" Stretch="Fill" Fill="#FF000000" Data="F1 M 19,50L 28,34L 63,34L 54,50L 19,50 Z M 19,28.0001L 35,28C 36,25 37.4999,24.0001 37.4999,24.0001L 48.75,24C 49.3023,24 50,24.6977 50,25.25L 50,28L 53.9999,28.0001L 53.9999,32L 27,32L 19,46.4L 19,28.0001 Z "/> 
    </Canvas> 
</ResourceDictionary> 

一点题外话,我也不知道该如何摆脱明确的缩放。这似乎是一个小问题,但我将不胜感激,如果这也可以解决。

总之,要尽可能这个有点太传神的定义移动到风格,我创建的代码为Visual

namespace extensions 
{ 
    public class AttachedProperties 
    { 
     public static readonly DependencyProperty VisualIconProperty = 
      DependencyProperty.RegisterAttached("VisualIcon", 
       typeof(System.Windows.Media.Visual), typeof(AttachedProperties), 
       new PropertyMetadata(default(System.Windows.Media.Visual))); 

     public static void SetVisualIcon(UIElement element, System.Windows.Media.Visual value) 
     { 
      element.SetValue(VisualIconProperty, value); 
     } 
     public static System.Windows.Media.Visual GetVisualIcon(UIElement element) 
     { 
      return (System.Windows.Media.Visual)element.GetValue(VisualIconProperty); 
     } 
    } 
} 

类型的附加属性重新定义菜单项

<MenuItem Header="_Open" 
      Style="{StaticResource MenuItemStyle}" 
      extensions:AttachedProperties.VisualIcon="{DynamicResource appbar.folder.open}" /> 

并尝试创建一个读取VisualIcon属性以设置图标内容的样式

<Style x:Key="MenuItemStyle" TargetType="MenuItem"> 
    <Setter Property="MenuItem.Icon"> 
     <Setter.Value> 
      <Viewbox> 
       <ContentControl RenderTransformOrigin="0.5,0.5"> 
        <ContentControl.Content> 
         <!-- this would work but doesn't reference the VisualIcon property.. <DynamicResource ResourceKey="appbar.folder.open"/> --> 
         <!-- these do not --> 
         <Binding Path="(extensions:AttachedProperties.VisualIcon)" RelativeSource="{RelativeSource FindAncestor, AncestorType=MenuItem}"/>--> 
         <!--<Binding Path="(extensions:AttachedProperties.VisualIcon)" RelativeSource="{RelativeSource TemplatedParent}"/>--> 
         <!--<Binding Path="(extensions:AttachedProperties.VisualIcon)" RelativeSource="{RelativeSource Self}"/>--> 
        </ContentControl.Content> 
        <ContentControl.RenderTransform> 
         <TransformGroup> 
          <ScaleTransform ScaleX="2" ScaleY="2"/> 
         </TransformGroup> 
        </ContentControl.RenderTransform> 
       </ContentControl> 
      </Viewbox> 
     </Setter.Value> 
    </Setter> 
</Style> 

但失败。引用具有DynamicResource和静态密钥的资源,但我无法使用附加属性进行任何绑定。

由于我是WPF初学者,我不确定这是否是一个好方法。

[EDIT1]

我测试所有提供的解决方案。 Grek40answer没有为我显示任何图标,无论是在设计视图还是在运行时;也许我做了完全错误的事情。

grx70的第二种方法second answer它对我来说最有希望,因为它在设计视图中以及在运行时可靠地显示图标。 然而,似乎Win7和Win10之间我不明白。我在Win7和Win10上测试了相同的源代码(在外部驱动器上)。对于Win10,它看起来不错,但Win7绘制的图标太大了。

(注:原因是this comment给)

Difference Win7/Win10

这里的窗口testcode:

<Window.Resources> 
    <Style x:Key="MenuItemStyle" TargetType="controls:MenuItemEx"> 
     <Setter Property="MenuItem.Icon"> 
      <Setter.Value> 
       <Viewbox> 
        <ContentControl RenderTransformOrigin="0.5,0.5"> 
         <ContentControl.Content> 
          <Binding Path="(extensions:AttachedProperties.VisualIcon)" RelativeSource="{RelativeSource FindAncestor, AncestorType=MenuItem}"/> 
         </ContentControl.Content> 
         <ContentControl.RenderTransform> 
          <TransformGroup> 
           <ScaleTransform ScaleX="2" ScaleY="2"/> 
          </TransformGroup> 
         </ContentControl.RenderTransform> 
        </ContentControl> 
       </Viewbox> 
      </Setter.Value> 
     </Setter> 
    </Style> 
    <Style x:Key="MenuItemStyle2" TargetType="MenuItem"> 
     <Setter Property="uihelpers:MenuItemHelper.IsEnabled" Value="True" /> 
     <Setter Property="MenuItem.Icon"> 
      <Setter.Value> 
       <Viewbox> 
        <ContentControl RenderTransformOrigin="0.5,0.5" 
         Content="{Binding 
         Path=(uihelpers:MenuItemHelper.MenuItem).(extensions:AttachedProperties.VisualIcon), 
         RelativeSource={RelativeSource AncestorType=Viewbox}}"> 
         <ContentControl.RenderTransform> 
          <TransformGroup> 
           <ScaleTransform ScaleX="2" ScaleY="2"/> 
          </TransformGroup> 
         </ContentControl.RenderTransform> 
        </ContentControl> 
       </Viewbox> 
      </Setter.Value> 
     </Setter> 
    </Style> 

    <Style x:Key="MenuItemStyle3" TargetType="{x:Type MenuItem}"> 
     <Style.Resources> 
      <DataTemplate x:Key="MenuItemStyle3dt" DataType="{x:Type Style}"> 
       <Path Style="{Binding}" 
         Stretch="Uniform" 
         HorizontalAlignment="Center" 
         VerticalAlignment="Center" /> 
      </DataTemplate> 
     </Style.Resources> 
    </Style> 
    <Style x:Key="appbar.folder.open.style3.local" TargetType="{x:Type Path}"> 
     <Setter Property="Fill" Value="Black" /> 
     <Setter Property="Data" Value="M0,26 L9,10 L44,10 L35,26 Z M0,4 L16,4 C17,1 18.5,0 18.5,0 L29.75,0 C30.3,0 31,0.7 31,1.25 L31,4 L34,4 L34,8 L8,8 L0,22.4 Z" /> 
    </Style> 
    <extensions:PathStyle x:Key="appbar.folder.open.style3b"> 
     <Setter Property="Path.HorizontalAlignment" Value="Center" /> 
     <Setter Property="Path.VerticalAlignment" Value="Center" /> 
     <Setter Property="Path.Stretch" Value="Uniform" /> 
     <Setter Property="Path.Fill" Value="Black" /> 
     <Setter Property="Path.Data" Value="M0,26 L9,10 L44,10 L35,26 Z M0,4 L16,4 C17,1 18.5,0 18.5,0 L29.75,0 C30.3,0 31,0.7 31,1.25 L31,4 L34,4 L34,8 L8,8 L0,22.4 Z" /> 
    </extensions:PathStyle> 
    <DataTemplate DataType="{x:Type extensions:PathStyle}"> 
     <Path Style="{Binding}" /> 
    </DataTemplate> 

    <Canvas x:Key="appbar.folder.open.style4.local" x:Shared="False" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
     <Path Width="44" Height="26" Canvas.Left="19" Canvas.Top="24" Stretch="Fill" Fill="#FF000000" Data="F1 M 19,50L 28,34L 63,34L 54,50L 19,50 Z M 19,28.0001L 35,28C 36,25 37.4999,24.0001 37.4999,24.0001L 48.75,24C 49.3023,24 50,24.6977 50,25.25L 50,28L 53.9999,28.0001L 53.9999,32L 27,32L 19,46.4L 19,28.0001 Z "/> 
    </Canvas> 
    <Viewbox x:Key="MenuItemStyle4.Icon" x:Shared="False"> 
     <ContentControl Content="{Binding Path=Tag,RelativeSource={RelativeSource AncestorType=MenuItem}}" RenderTransformOrigin="0.5,0.5"> 
      <ContentControl.RenderTransform> 
       <TransformGroup> 
        <ScaleTransform ScaleX="2" ScaleY="2"/> 
       </TransformGroup> 
      </ContentControl.RenderTransform> 
     </ContentControl> 
    </Viewbox> 
    <Style x:Key="MenuItemStyle4" TargetType="MenuItem"> 
     <Setter Property="Icon" Value="{StaticResource MenuItemStyle4.Icon}"/> 
    </Style> 
</Window.Resources> 
<Grid> 
    <Menu x:Name="menu" Height="19" Margin="10,10,10.333,0" VerticalAlignment="Top"> 
     <MenuItem Header="_File"> 
      <controls:MenuItemEx Header="_Open" 
         Style="{StaticResource MenuItemStyle}" 
         extensions:AttachedProperties.VisualIcon="{DynamicResource appbar.folder.open}" /> 
      <MenuItem Header="_Open" 
         Style="{StaticResource MenuItemStyle2}" 
         extensions:AttachedProperties.VisualIcon="{DynamicResource appbar.folder.open}" /> 
      <MenuItem Header="_Open" 
         Style="{StaticResource MenuItemStyle3}" 
         Icon="{DynamicResource appbar.folder.open.style3}" /> 
      <MenuItem Header="_Open" 
         Style="{StaticResource MenuItemStyle3}" 
         Icon="{DynamicResource appbar.folder.open.style3.local}" /> 
      <MenuItem Header="_Open" Icon="{DynamicResource appbar.folder.open.style3b}" /> 
      <MenuItem Header="_Open" 
         Style="{StaticResource MenuItemStyle4}" 
         Tag="{DynamicResource appbar.folder.open}" /> 
      <MenuItem Header="_Open" 
         Style="{StaticResource MenuItemStyle4}" 
         Tag="{DynamicResource appbar.folder.open.style4.local}" /> 
      <MenuItem Header="_Save"> 
       <MenuItem.Icon> 
        <Viewbox> 
         <ContentControl Content="{DynamicResource appbar.save}" RenderTransformOrigin="0.5,0.5"> 
          <ContentControl.RenderTransform> 
           <TransformGroup> 
            <ScaleTransform ScaleX="2" ScaleY="2"/> 
           </TransformGroup> 
          </ContentControl.RenderTransform> 
         </ContentControl> 
        </Viewbox> 
       </MenuItem.Icon> 
      </MenuItem> 
     </MenuItem> 
    </Menu> 
    <Menu x:Name="menu2" Height="19" Margin="9,32,11.333,0" VerticalAlignment="Top" ItemContainerStyle="{StaticResource MenuItemStyle4}"> 
     <MenuItem Header="_File"> 
      <MenuItem Header="_Open" 
         Tag="{DynamicResource appbar.folder.open.style4.local}" /> 
      <MenuItem Header="_Open" 
         Tag="{DynamicResource appbar.folder.open}" /> 
     </MenuItem> 
    </Menu> 
</Grid> 

,这里是全球资源:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      x:Key="appbar.folder.open" x:Shared="False" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
     <Path Width="44" Height="26" Canvas.Left="19" Canvas.Top="24" Stretch="Fill" Fill="#FF000000" Data="F1 M 19,50L 28,34L 63,34L 54,50L 19,50 Z M 19,28.0001L 35,28C 36,25 37.4999,24.0001 37.4999,24.0001L 48.75,24C 49.3023,24 50,24.6977 50,25.25L 50,28L 53.9999,28.0001L 53.9999,32L 27,32L 19,46.4L 19,28.0001 Z "/> 
    </Canvas> 
    <Style x:Key="appbar.folder.open.style3" TargetType="{x:Type Path}"> 
     <Setter Property="Fill" Value="Black" /> 
     <Setter Property="Data" Value="M0,26 L9,10 L44,10 L35,26 Z M0,4 L16,4 C17,1 18.5,0 18.5,0 L29.75,0 C30.3,0 31,0.7 31,1.25 L31,4 L34,4 L34,8 L8,8 L0,22.4 Z" /> 
    </Style> 
</ResourceDictionary> 

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      x:Key="appbar.save" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
     <Path Width="34.8333" Height="34.8333" Canvas.Left="20.5833" Canvas.Top="20.5833" Stretch="Fill" Fill="#FF000000" Data="F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z "/> 
    </Canvas> 
</ResourceDictionary> 

它们在app中合并。XAML:

<Application.Resources> 
    <ResourceDictionary > 
     <ResourceDictionary.MergedDictionaries> 
      <ResourceDictionary Source="icons/appbar.folder.open.xaml"/> 
      <ResourceDictionary Source="icons/appbar.save.xaml"/> 
     </ResourceDictionary.MergedDictionaries> 
    </ResourceDictionary> 
</Application.Resources> 

原因的图标偏移是我把他们或多或少的1:1,从github.com/Templarian/WindowsIcons并希望,因为他们在这个格式提供(我也试图将其保存为画笔,首先使用Inkscape,然后使用Expression Design),像这样使用它们是很常见的。

+0

您是否获得在输出窗口任何约束力相关的错误? – Grx70

+0

@ Grx70为三种不同的方法,只有FindAncestor日志:System.Windows.Data错误:4:找不到与参考'RelativeSource FindAncestor,AncestorType ='System.Windows绑定的源。Controls.MenuItem',AncestorLevel ='1''。 BindingExpression:路径=(0);的DataItem = NULL;目标元素是'ContentControl'(Name ='');目标属性是'内容'(类型'对象')。奇怪的是,“自我”方法在设计视图中将“(0)”显示为图标,但在运行时不显示。 –

+1

根据[本QA](https://stackoverflow.com/questions/15725729/wpf-menuitem-icon-dimensions)'MenuItem'高度因使用的主题而异。不幸的是,没有确定“最佳”图标大小的通用方法 - 例如在_Aero_主题(_Windows 7_的默认设置)中没有任何限制(在布局的度量传递过程中始终存在无限可用空间 - 这来自“MenuItem”模板),因此您的结果(图标变得“一样大因为他们想要“)。可能会有一些解决方法让事情变得更不起作用,但这需要一个不同的问题。 – Grx70

回答

1

由于它看起来像XY problem,我会给你另一种方法来完成你的目标。

首先,您的appbar.folder.open资源过于复杂。 Canvas是完全多余的(您可以通过设置其Margin来抵消Path)。这些数字在Path之间偏移了19,24,其与Path结合在Canvas中抵消导致必须使用Viewbox连同ScaleTransform。而且,你的资源是不可重用的,因为它只能被加载到可视化树中一次,所以它只会在它引用的最后一个地方可见(它会在以前的所有地方卸载)。我的建议是为Path创建一个Style而不是 - 它不仅可重用,而且可扩展,因为您可以在应用样式之后修改目标Path上的其他属性。这里有一个最小的风格来完成这项工作(我翻译的数据,以便它不再偏移):

<Style x:Key="appbar.folder.open" TargetType="{x:Type Path}"> 
    <Setter Property="Fill" Value="Black" /> 
    <Setter Property="Data" Value="M0,26 L9,10 L44,10 L35,26 Z M0,4 L16,4 C17,1 18.5,0 18.5,0 L29.75,0 C30.3,0 31,0.7 31,1.25 L31,4 L34,4 L34,8 L8,8 L0,22.4 Z" /> 
</Style> 

其次,我不太明白你的AttachedProperties.VisualIcon附加属性的目的。在我的方法中,它是完全多余的。此外,我相信这是使设计师不能正确显示图标并禁用项目代码的部分。唯一的问题是,如果我们设置MenuItem.Icon="{DynamicResource appbar.folder.open}",我们将获得System.Windows.Style文本而不是图标。并且DynamicResourceExtension不支持转换引用的资源开箱即用。但是,有一个聪明的把戏,我们可以用它来使事情的工作 - 只是提供DataType="{x:Type Style}"一个隐含DataTemplate将被自动应用:

<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}"> 
    <Style.Resources> 
     <DataTemplate DataType="{x:Type Style}"> 
      <Path Style="{Binding}" 
        Stretch="Uniform" 
        HorizontalAlignment="Center" 
        VerticalAlignment="Center" /> 
     </DataTemplate> 
    </Style.Resources> 
</Style> 

我们设置一些附加属性的Path,使其很符合图标区。

现在我们需要做的是引用风格,并有图标就显示在MenuItem

<Menu (...)> 
    <MenuItem Header="_Open" 
       Style="{StaticResource MenuItemStyle}" 
       Icon="{DynamicResource appbar.folder.open}" /> 
</Menu> 

这种方法有它的设计师作品,即使项目代码的额外优势禁用(至少它对我来说)。

编辑

如果你想完全独立的和自动化的东西,你可以继承Style

public class PathStyle : Style 
{ 
    public PathStyle() 
    { 
     TargetType = typeof(Path); 
    } 
} 

而在你的资源字典提供隐含DataTemplate

<local:PathStyle x:Key="appbar.folder.open"> 
    <Setter Property="Path.HorizontalAlignment" Value="Center" /> 
    <Setter Property="Path.VerticalAlignment" Value="Center" /> 
    <Setter Property="Path.Stretch" Value="Uniform" /> 
    <Setter Property="Path.Fill" Value="Black" /> 
    <Setter Property="Path.Data" Value="M0,26 L9,10 L44,10 L35,26 Z M0,4 L16,4 C17,1 18.5,0 18.5,0 L29.75,0 C30.3,0 31,0.7 31,1.25 L31,4 L34,4 L34,8 L8,8 L0,22.4 Z" /> 
</local:PathStyle> 
<DataTemplate DataType="{x:Type local:PathStyle}"> 
    <Path Style="{Binding}" /> 
</DataTemplate> 

我感动了所有从DataTemplateStyle的属性,以便它可以被覆盖在其他风格。但请注意,您需要完全限定属性名称,即使用Path.Data而不是简单地使用Data

现在,所有你需要是引用的资源在您的视图:

<MenuItem Icon="{DynamicResource appbar.folder.open}" (...) /> 

甚至:

<ContentPresenter Content="{DynamicResource appbar.folder.open}" /> 

而且所有的魔法是由框架来完成。这种方法的好处是,你可以用含有例如更换您的ResourceDictionary

<Border x:Key="appbar.folder.open" x:Shared="False" Background="Red" /> 

所有的一切都仍然有效,而无需修改意见。

+0

我从来没见过'”之前......你有没有解释这种方法的任何源代码(有任何合理的使用场景)?它看起来对我来说真的很奇怪...... – grek40

+1

@ grek40我知道它看起来有点儿奇怪的是,我从来没有见过它,至于说明 - 我们有一个'Path'作为'Path',当它被应用时,创建一个图标,我们想把它设置为'MenuItem.Icon'。 'DynamicResourceExtension'不支持转换,'MenuItem'不包含'IconTemplate'和'IconTemplateSelector'属性,我们需要使用impl来使用impl icit'DataTemplate'将“Style”转换为'Path'。这是非常标准的方法。你认为数据类型是'Style'而不是某些任意的'FooViewModel'有一些风险吗? – Grx70

+0

编辑了这个问题,因为有太多的问题需要发表评论 –

1

诊断

为什么你绑定不工作的原因是这些:

  1. 对于RelativeSourceMode.Self - 因为VisualIcon附加属性没有明确设置在ContentControl和有null它的默认值。
  2. RelativeSourceMode.TemplatedParent - 因为ContentControl不是一个模板的一部分(如果是,这是不是从你所提供的代码明显,可能是模板化父的VisualIconnull
  3. 对于RelativeSourceMode.FindAncestor - 因为MenuItem不会将MenuItem.Icon属性的值设置为其逻辑子级,所以直到ContentControl加载到可视化树中,无法建立两者之间的关系。显然,在这种情况下,绑定会在此之前解析,并且由于某种原因在最后加载ContentControl时不会重新解析。

解决方案(S)

有可以采取的解决这一问题的几种方法,下面是其中的一些。

一集时ContentControl加载

您可以订阅以下处理程序的ContentControl.Loaded事件的唯一绑定:

private void ContentControl_Loaded(object sender, RoutedEventArgs e) 
{ 
    var control = (ContentControl)sender; 
    control.SetBinding(ContentControl.ContentProperty, new Binding 
    { 
     Path = new PropertyPath(AttachedProperties.VisualIconProperty), 
     RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) 
     { 
      AncestorType = typeof(MenuItem), 
     }, 
    }); 
} 

然后在你的XAML

<ContentControl 
    Content="{Binding 
     Path=(extensions:AttachedProperties.VisualIcon), 
     RelativeSource={RelativeSource AncestorType=MenuItem}}" 
    Loaded="ContentControl_Loaded" (...)> 
    (...) 
</ContentControl> 

请注意,如果风格是地方d在一个单独的文件的资源字典中,你必须遵循these instructions才能正常工作。

您也可以创建自定义MarkupExtension(例如,名称为DeferredBinding)来完成这项工作。

二,子类MenuItem并设置图标的逻辑子

没有太多的代码来写:

public class MyMenuItem : MenuItem 
{ 
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
    { 
     base.OnPropertyChanged(e); 
     if (e.Property == IconProperty) 
     { 
      if (e.OldValue != null) 
       RemoveLogicalChild(e.OldValue); 
      if (e.NewValue != null) 
       AddLogicalChild(e.NewValue); 
     } 
    } 
} 

但缺点是,你总是要记住用MyMenuItem而不是MenuItem

<local:MyMenuItem Header="_Open" 
     Style="{StaticResource MenuItemStyle}" 
     extensions:AttachedProperties.VisualIcon="{DynamicResource appbar.folder.open}" /> 

和:

<Style x:Key="MenuItemStyle" TargetType="local:MyMenuItem"> 
    (...) 
</Style> 

在这种情况下,您还需要绑定FindAncestor模式。

三,创建一个辅助类,将允许通过附加属性

下面的辅助类包含两个依赖属性访问从ContentControl相关MenuItem - IsEnabled(我相信这是自我解释),以及MenuItem,这是只读并拥有实际MenuItem,其上的目标图标设置:

public static class MenuItemHelper 
{ 
    /**** Here are the important parts: ****/ 

    //When IsEnabled changes we need to either hook things up or do the cleanup 
    private static void HandleIsEnabledChanged(
     DependencyObject d, 
     DependencyPropertyChangedEventArgs e) 
    { 
     var item = (MenuItem)d; 
     if ((bool)e.NewValue) 
      //We set MenuItem attached property for current Icon 
      HandleIconChanged(null, item, EventArgs.Empty); 
     else 
      //We clear the value of MenuItem attached property 
      HandleIconChanged(item.Icon, item, EventArgs.Empty); 
    } 

    //By using an extension method we get hold of the old value without the need 
    //to maintain any kind of dictionary, so we don't need to worry about memory leaks 
    private static void HandleIconChanged(
     this object oldValue, 
     object sender, 
     EventArgs e) 
    { 
     var item = (MenuItem)sender; 
     if (oldValue is DependencyObject oldIcon) 
      SetMenuItem(oldIcon, null); 
     if (item.Icon is DependencyObject newIcon) 
      SetMenuItem(newIcon, item); 
     //We need to remove the old handler, because it relates to the old icon 
     DependencyPropertyDescriptor 
      .FromProperty(MenuItem.IconProperty, item.GetType()) 
      .RemoveValueChanged(item, item.Icon.HandleIconChanged); 
     //We add new handler, so that when the icon changes we get correct old icon 
     DependencyPropertyDescriptor 
      .FromProperty(MenuItem.IconProperty, item.GetType()) 
      .AddValueChanged(item, item.Icon.HandleIconChanged); 
    } 

    /**** The rest is just DP boilerplate code ****/ 

    private static readonly DependencyPropertyKey MenuItemPropertyKey = 
     DependencyProperty.RegisterAttachedReadOnly(
      name: "MenuItem", 
      propertyType: typeof(MenuItem), 
      ownerType: typeof(MenuItemHelper), 
      defaultMetadata: new PropertyMetadata(null)); 

    public static readonly DependencyProperty MenuItemProperty = 
     MenuItemPropertyKey.DependencyProperty; 

    public static MenuItem GetMenuItem(DependencyObject d) 
     => (MenuItem)d.GetValue(MenuItemProperty); 

    private static void SetMenuItem(DependencyObject d, MenuItem value) 
     => d.SetValue(MenuItemPropertyKey, value); 

    public static readonly DependencyProperty IsEnabledProperty = 
     DependencyProperty.RegisterAttached(
      name: "IsEnabled", 
      propertyType: typeof(bool), 
      ownerType: typeof(MenuItemHelper), 
      defaultMetadata: new PropertyMetadata(false, HandleIsEnabledChanged)); 

    public static bool GetIsEnabled(MenuItem item) 
     => (bool)item.GetValue(IsEnabledProperty); 

    public static void SetIsEnabled(MenuItem item, bool value) 
     => item.SetValue(IsEnabledProperty, value); 
} 

然后你只需要设置在MenuItemMenuItemHelper.IsEnabled="True",你可以使用MenuItemHelper.MenuItem为绑定(请记住,这将在根ELE设置图标的换货 - 你的情况Viewbox):

<Style x:Key="MenuItemStyle" TargetType="MenuItem"> 
    <Setter Property="extensions:MenuItemHelper.IsEnabled" Value="True" /> 
    <Setter Property="MenuItem.Icon"> 
     <Setter.Value> 
      <Viewbox> 
       <ContentControl 
        Content="{Binding 
         Path=(extensions:MenuItemHelper.MenuItem).(extensions:AttachedProperties.VisualIcon), 
         RelativeSource={RelativeSource AncestorType=Viewbox}}" (...)> 
        (...) 
       </ContentControl> 
      </Viewbox> 
     </Setter.Value> 
    </Setter> 
</Style> 

我个人最喜欢的是#III,因为它是最通用的三个,但也许在您的特定情况下其他的解决方案可能被证明是更适用。

+0

如果你对代码'DeferredBinding'感兴趣,请告诉我。 – Grx70

+0

非常感谢您的详细解释!我尝试了2和3,两者都工作(不得不修复MenuItemHelper中的旧/新图标;或者是我还不知道的语法?)。 对不起,我以前没有提到它,但如果解决方案也可以在设计视图中工作,那将会很棒。 DeferredBinding可以做到吗?或者是否存在另一种方法,即使它是一种丑陋的方法,也可以在设计视图中工作? –

+1

没错,我正要解决这个问题 - 似乎你在设计器中禁用了你的项目代码(因此你提到的“(0)”)。在设计器底部的面板中查找“启用/禁用项目代码”切换按钮(我相信这是最右边的一个),然后将其打开。可能需要重新启动才能生效。 – Grx70

0

你的风格方法的问题是,Viewbox将在应用相同样式时在多个项目之间共享。这可以通过将Viewbox作为与x:Shared="False"分开的资源来避免。如果你想坚持你当前的方法,你应该把它变成一个非共享资源,所以它变得可以重复使用

为了演示的目的,我设置了MenuItem.Tag属性,但同样应该可以你的附属财产的想法。

资源:

<!--Notice the added x:Shared--> 
<Canvas x:Key="appbar.folder.open" x:Shared="False" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
    <Path Width="44" Height="26" Canvas.Left="19" Canvas.Top="24" Stretch="Fill" Fill="#FF000000" Data="F1 M 19,50L 28,34L 63,34L 54,50L 19,50 Z M 19,28.0001L 35,28C 36,25 37.4999,24.0001 37.4999,24.0001L 48.75,24C 49.3023,24 50,24.6977 50,25.25L 50,28L 53.9999,28.0001L 53.9999,32L 27,32L 19,46.4L 19,28.0001 Z "/> 
</Canvas> 

<!--Another icon for purpose of demonstration--> 
<Canvas x:Key="appbar.folder.close" x:Shared="False" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> 
    <Path Width="44" Height="26" Canvas.Left="19" Canvas.Top="24" Stretch="Fill" Fill="#FFFF0000" Data="F1 M 19,50L 28,34L 63,34L 54,50L 19,50 Z M 19,28.0001L 35,28C 36,25 37.4999,24.0001 37.4999,24.0001L 48.75,24C 49.3023,24 50,24.6977 50,25.25L 50,28L 53.9999,28.0001L 53.9999,32L 27,32L 19,46.4L 19,28.0001 Z "/> 
</Canvas> 

<Viewbox x:Key="MenuItemStyle.Icon" x:Shared="False"> 
    <ContentControl Content="{Binding Path=Tag,RelativeSource={RelativeSource AncestorType=MenuItem}}" RenderTransformOrigin="0.5,0.5"> 
     <ContentControl.RenderTransform> 
      <TransformGroup> 
       <ScaleTransform ScaleX="2" ScaleY="2"/> 
      </TransformGroup> 
     </ContentControl.RenderTransform> 
    </ContentControl> 
</Viewbox> 

<Style x:Key="MenuItemStyle" TargetType="MenuItem"> 
    <Setter Property="Icon" Value="{StaticResource MenuItemStyle.Icon}"/> 
</Style> 

用法:

<Menu x:Name="menu" Height="19" Margin="10,10,10.333,0" VerticalAlignment="Top" ItemContainerStyle="{StaticResource MenuItemStyle}"> 
    <MenuItem Header="_Open" Tag="{DynamicResource appbar.folder.open}"/> 
    <MenuItem Header="_Open 2" Tag="{DynamicResource appbar.folder.open}"/> 
    <MenuItem Header="_Close" Tag="{DynamicResource appbar.folder.close}"/> 
</Menu> 
+0

我尝试了一些组合(请参阅更新后的问题),但是没有人在设计视图和运行时都没有显示任何图标。也许我做了完全错误的事情。 –

+0

@wonkorealtime它只是一个小小的误解...每个子菜单都有自己的'ItemContainerStyle',所以你需要将它添加到文件菜单或使用隐式样式资源来应用子项:''。但是,这不是最适合设计人员的版本,图标只能在运行时正常运行。 – grek40