MenuItem style when ItemsSource is used using setter Property with ResourceDictionaries or MergedDictionaries

StackOverflow https://stackoverflow.com/questions/21593589

Pergunta

I've got a Context Menu, and is styled correctly from a ResourceDictionary that is loaded with XamlReader.Load(). The style key that I'm using is a DynamicResource that I've called styleBanner.

In this Context Menu I have one Menu Item called Skins, which is also styled correctly with the save above dynamic resource styleBanner. But this menu-item has it's sub-menu-items data-bound to an ItemsSource in the data context View Model, and this is also working correctly.

My trouble is that the child menu items are not styled correctly.

Here is what is working, but not getting styled at all:

<Window.ContextMenu>
    <ContextMenu DataContext="TimersHostViewModel" Name="TimersHostContextMenu" Style="{DynamicResource styleBanner}">
        <MenuItem Name="Skins" Header="Skins" ItemsSource="{Binding Source={StaticResource TimersHostViewModel}, Path=Skins}" Style="{DynamicResource styleBanner}">
            <MenuItem.ItemContainerStyle>
                <Style TargetType="MenuItem">
                    <Setter Property="Header" Value="{Binding Path=SkinName}"/>
                    <Setter Property="Command" Value="{Binding Source={StaticResource TimersHostViewModel}, Path=TimersHostContextMenuClickCommand}"/>
                    <Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
                </Style>
            </MenuItem.ItemContainerStyle>
        </MenuItem>
    </ContextMenu>
</Window.ContextMenu>

So here is one thing that I've tried, I tried adding the following line:

<Setter Property="Style" Value={DynamicResource styleBanner}"/>

Like this:

<Style TargetType="MenuItem">
    <Setter Property="Header" Value="{Binding Path=SkinName}"/>
    <Setter Property="Style" Value="{DynamicResource styleBanner}"/>
    <Setter Property="Command" Value="{Binding Source={StaticResource TimersHostViewModel}, Path=TimersHostContextMenuClickCommand}"/>
    <Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
</Style>

I get an exception when I try that: System.ArgumentException {"Style object is not allowed to affect the Style property of the object to which it applies."}

So I tried this to change the added line above as follows:

<Setter Property="Template" Value="{DynamicResource styleBanner}"/>

Then I get a different exception: System.InvalidCastException {"Unable to cast object of type 'System.Windows.Style' to type 'System.Windows.FrameworkTemplate'."}

So what is the correct way to do this? I've searched all over the internet and Stackoverflow without a clue.


EDIT: Ok I see that the using the following now works for getting the style set on the child menu items:

<Setter Property="ItemContainerStyle" Value="{DynamicResource styleBanner}"/>

But for some reason the styleBanner background is not used for the children menu items.

I should have posted the styleBanner from my ResourceDictionary so here it is:

<!-- Banner Style -->
<Style x:Key="styleBanner">
  <Setter Property="StackPanel.Background">
    <Setter.Value>
      <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
        <GradientStop Color="DarkGray" Offset="0.1" />
        <GradientStop Color="Black" Offset="1" />
      </LinearGradientBrush>
    </Setter.Value>
  </Setter>
  <Setter Property="TextBlock.Foreground" Value="White" />
  <Setter Property="TextBlock.FontFamily" Value="TR2N" />
</Style>

Here is what I have for the context menu now:

<Window.ContextMenu>
    <ContextMenu DataContext="TimersHostViewModel" Name="TimersHostContextMenu" Style="{DynamicResource styleBanner}">
        <MenuItem Name="Skins" Header="Skins" ItemsSource="{Binding Source={StaticResource TimersHostViewModel}, Path=Skins}" Style="{DynamicResource styleBanner}">
            <MenuItem.ItemContainerStyle>
                <Style TargetType="MenuItem">
                    <Setter Property="Header" Value="{Binding Path=SkinName}"/>
                    <Setter Property="Command" Value="{Binding Source={StaticResource TimersHostViewModel}, Path=TimersHostContextMenuClickCommand}"/>
                    <Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
                    <Setter Property="ItemContainerStyle" Value="{DynamicResource styleBanner}"/>
                </Style>
            </MenuItem.ItemContainerStyle>
        </MenuItem>
    </ContextMenu>
</Window.ContextMenu>

Here is an image showing the difference in styles that I'm getting: Difference in styles applied to different sections levels of the context menu.

So you can see that the font & font color are applied, but the styleBanner background with the gradient brush as defined in the ResourceDictionary is not.

Actually if you look carefully I played a little trick, the styleBanner from the ResourceDictionary above was posted from a BlackSkin.xaml ResourceDictionary and it has a gradient background that should be dark grey, but the screenshot shows a blue background, because I've got a BlueSkin.xaml ResourceDictionary, but they are all similar and all have the same behavior, so here is the styleBanner from the BlueSkin.xaml ResourceDictionary, for completeness:

<!-- Banner Style -->
<Style x:Key="styleBanner">
  <Setter Property="StackPanel.Background">
    <Setter.Value>
      <LinearGradientBrush StartPoint="0,0.25" EndPoint="1,0.5">
        <GradientStop Color="#CC0088DD" Offset="0.3" />
        <GradientStop Color="#3300FFFF" Offset="0.85" />
      </LinearGradientBrush>
    </Setter.Value>
  </Setter>
  <Setter Property="TextBlock.Foreground" Value="Yellow" />
  <Setter Property="TextBlock.FontFamily" Value="Comic Sans MS" />
</Style>
Foi útil?

Solução

You cannot set the Style property within Style.

Set StyleBanner as ItemContainerStyle within a Style e.g. ContextMenuStyle

E.g. Lets name your Style name as ContextMenuStyle and set its ItemContainer Style to StyleBanner

<Style x:Key="ContextMenuStyle" TargetType="MenuItem">
    <Setter Property="Header" Value="{Binding Path=SkinName}"/>
    <Setter Property="Command" Value="{Binding Source={StaticResource TimersHostViewModel}, Path=TimersHostContextMenuClickCommand}"/>
    <Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
    <Setter Property="ItemContainerStyle" Value="{DynamicResource styleBanner}"/>
</Style>

Now for your Context Menu you can assign this style:

<ContextMenu.Style>
    <StaticResource ResourceKey="ContextMenuStyle"></StaticResource>
</ContextMenu.Style>

Hope you get the idea... Decide which belongs to style and which belongs to ContextMenuStyle and set Style for the control and you can set ContextMenuStyle within the style. You can even have nested ContextMenuStyle within the styleBanner which will inturn apply to the sub menus

Edit:

You are right, the background does not get carried over to the child menu items. In that case the only option now left is to define the ControlTemplate with the background, try this for the :

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate>
            <Border >
                <Border.Background>
                    <LinearGradientBrush StartPoint="0,0.25" EndPoint="1,0.5">
                        <GradientStop Color="#CC0088DD" Offset="0.3" />
                        <GradientStop Color="#3300FFFF" Offset="0.85" />
                    </LinearGradientBrush>
                </Border.Background>
                <Button Content="{Binding Path=SkinName}" Command="{Binding Source={StaticResource TimersHostViewModel}, Path=TimersHostContextMenuClickCommand}" 
                        CommandParameter="{Binding Path=SkinName}"
                        Foreground="Yellow" FontFamily="Comic Sans MS"></Button>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Outras dicas

You can use the "BasedOn" property:

<MenuItem.ItemContainerStyle>
  <Style BasedOn="{StaticResource ISAMenu}" TargetType="MenuItem">
      <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}, Path=DataContext.ChangeLanguageCommand}" />
      <Setter Property="CommandParameter" Value="{Binding}" />
  </Style>
</MenuItem.ItemContainerStyle>

and then in your App.xaml the just reference a dictionary

<ResourceDictionary.MergedDictionaries>
   <ResourceDictionary Source="Assets/ResourceDictionaries/NavBarMenu.xaml" />
 </ResourceDictionary.MergedDictionaries>

Finaly in the NavBarMenu.xaml

<Style x:Key="ISAMenu" TargetType="{x:Type MenuItem}">

It's an old question but I spend some hours to find out another clean solutions so I hope it can help someone.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top