2017-08-04 31 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