Prevent ItemContainerStyle from overriding already set Style - c#

Is there a way of preventing a ItemContainerStyle from overriding an already set Style (via <Style TargetType="{x:Type MenuItem}">) for instance ?
A style for a MenuItem is already defined within a ResourceDictionary XAML file, which is loaded on App startup :
<ResourceDictionary>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{DynamicResource TextForeground}"/>
.. and so on
</Style>
</ResourceDictionary>
I have the following MenuItem XAML definition. The MenuItem is wrapped inside a ContextMenu of a generic TextBlock (just worth mentioning I guess). All goes well with the menu itself, yet its children (the actual values of the Enum) get a different style, since ItemContainerStyle overrides it :
<MenuItem Header="DisplayType"
Name="DisplayTypeMenu"
ItemsSource="{Binding Source={StaticResource DisplayTypeValues}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="MenuItem.IsCheckable" Value="True" />
<Style.Triggers>
<Trigger Property="MenuItem.Header"
Value="{x:Static enums:DisplayType.Description}" >
<Setter Property="MenuItem.IsChecked" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The ItemContainerStyle stems from another question of mine.
The MenuItem is placed within other layers, the top layer being a custom ContentControl :
public class SomeGradientPanel : ContentControl
{
public SomeGradientPanel ()
{
DefaultStyleKey = typeof(SomeGradientPanel );
}
}
The code above seems to be a good candidate for the source of the problem !?
Thus, the complete structure is :
<SomeNameSpace:SomeGradientPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="SomeLabel">
<TextBlock.ContextMenu>
<ContextMenu>
<!-- The MenuItem code snippet from above !-->
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</SomeNameSpace:SomeGradientPanel>
Can I refer to the already defined Style for the MenuItem within the ItemContainerStyle ? The Style override only occurs on the children of the said MenuItem, the parent has the expected style.
Thank you for your input !

Have you tried
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">

Related

WPF MenuItem Icon sharing

I want to bind icons to the MenuItem controls where these items are dynamically created. I tried to set the x:Shared attribute to False but always only the last item has icon.
Here is my style for the MenuItems ItemContainerStyle code:
<Window.Resources>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" x:Shared="False">
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding IconSource}" />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And the MenuItem definition:
<MenuItem Header="Workspaces" ItemsSource="{Binding WorkspaceItems}" Icon="{StaticResource BranchIcon}" ItemContainerStyle="{StaticResource MenuItemStyle}" />
I have already tried to set this Shared attribute on the Image control but no luck.
Any suggestion?
You are almost there!
First of all: don't be confuse by Template vs Style.
When you are setting Icon property to an Image control, only one copy is created. As a control can have only one parent, it is removed from the previous parent each time it's re-assigned.
That's why you see only one icon.
You have 2 solutions for what you want:
use datatemplate instead, and redefine the whole Template of a MenuItem
use a style with a shared image component (what you tried to achieve)
In your example the only error is that the Shared attribute should be false on the Image resource, not on the whole style. This should work:
<Window.Resources>
<Image x:Key="MenuIconImage" x:Shared="false" Source="{Binding IconSource}"/>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Icon" Value="{StaticResource MenuIconImage}">
</Setter>
</Style>
</Window.Resources>
Hope it helps.

Caliburn Micro Action in ContextMenu with ControlTemplate

I have a custom component with a ContextMenu in a ControlTemplate. I've read a couple of posts with people having trouble getting their actions to work in a ContextMenu. I've tried the workarounds posted there but I cannot get it to work. I keep getting No target found for method Open. None of these solutions used a ControlTemplate, however. I've tried to bind the model to the contextmenu, to the menuitems, to use TargetWithoutContext property, but none seem to work.
<components2:ImageBlock Background="Transparent" x:Name="ShareButton" Margin="0,0,7,0" >
<components2:ImageBlock.Style>
<Style TargetType="{x:Type components2:ImageBlock }">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type components2:ImageBlock }">
<Grid x:Name="ContentGrid" Background="{TemplateBinding Background}" MinHeight="30" ToolTip="{TemplateBinding ToolTip}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Facebook" cal:Message.Attach="Open(0)"/>
<MenuItem Header="Twitter" cal:Message.Attach="Open(1)]" />
<MenuItem Header="Tumblr" cal:Message.Attach="Open(2)]" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</components2:ImageBlock.Style>
</components2:ImageBlock>
How can I get the Open to work at my viewmodel behind the view?
Got it to work through a DependencyObject that is added to the ImageBlock, where I had to set the PlacementTarget of the ContextMenu to the ImageBlock. Strangely, setting the PlacementTarget of the ContextMenu through {Binding ...} to ImageBlock directly didn't work.

Inversion of inheritance in WPF styles

I've got a UserControl that contains a button:
<Button Content="Button"/>
And a style:
<Style TargetType="Button">
<Setter Property="Background" Value="Blue"/>
</Style>
The parent window (or another UserControl) may set another more general style:
<Style TargetType="Button">
<Setter Property="Background" Value="Red"/>
</Style>
The result is (what is obvious) that parent buttons will have more general style (Red) and my user control will have buttons with more specific style (Blue).
I'm wondering how to invert such behaviour in order to achieve something like setting the default style in my custom user control which could be then overriden in parent control or window if necessary?
The key is, that default style is defined first in custom user control and it is overriden automaticly by its parent. That is way I called it an inversion.
The imaginary example of the solution maight look like the following:
<Style TargetType="Button" StylePriority="Default">
<Setter Property="Background" Value="Blue"/>
</Style>
The StylePriority could indicate that if there is no other style defined for that button, then the default style should be applied to it.
You could use dynamic resources.
A UserControl:
<UserControl x:Class="Example.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Example">
<UserControl.Resources>
<Style TargetType="local:UserControl1">
<Style.Resources>
<Style TargetType="Button" x:Key="UserControl1.DefaultButtonStyle">
<Setter Property="Background" Value="Red"/>
</Style>
</Style.Resources>
</Style>
</UserControl.Resources>
<Button Content="UserControlButton" Style="{DynamicResource UserControl1.DefaultButtonStyle}"/>
</UserControl>
And a Window:
<Window x:Class="Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Example">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="Blue" />
</Style>
</Window.Resources>
<StackPanel>
<local:UserControl1 >
<local:UserControl1.Resources>
<Style x:Key="UserControl1.DefaultButtonStyle" TargetType="Button"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="FontSize" Value="40" />
</Style>
</local:UserControl1.Resources>
</local:UserControl1>
<Button Content="WindowButton" />
</StackPanel>
</Window>
If you remove the style for the control in the window, the default user control button style will be applied.
Create a dependency property in your UserControl for the buttons colour, and then bind to it. You can specify a default value of blue for that property.
public static readonly DependencyProperty ButtonColorProperty =
DependencyProperty.Register("ButtonColor", typeof(Color), typeof(MyUserControl),
new PropertyMetadata(Colors.Blue));
public Color State
{
get { return (Color)this.GetValue(ButtonColorProperty); }
set { this.SetValue(ButtonColorProperty, value); }
}
<UserControl ...
x:Name="root">
<Button Content="Button" Background="{Binding ElementName=root, Path=ButtonColor}" />
</UserControl>
Then set that property to red where you want to use the UserControl.
<local:MyUserControl ButtonColor="Red" />

How to have multiple window resource styles for same element type in WPF

I have an element in my window, as below:
<Grid>
<DockPanel LastChildFill="True">
<Label Name="StatisticsLabel" DockPanel.Dock="Bottom"></Label>
<RichTextBox Style="{StaticResource FocusMode}" Name="RichTextBox1" />
</DockPanel>
</Grid>
I would like to swith between two styles at runtime depending of the state I need the control to be in.
I had assumed I could use the following code:
<Window.Resources>
<Style x:Name="FocusMode" TargetType="RichTextBox">
<Setter Property="VerticalScrollBarVisibility" Value="Disabled"></Setter>
</Style>
<Style x:Name="NormalMode" TargetType="RichTextBox">
<Setter Property="VerticalScrollBarVisibility" Value="Auto"></Setter>
</Style>
</Window.Resources>
Of course this isn't working.
Why does WPF not support multiple styles per element? Seems like a pretty basic requirement?
Otherwise, how do I achieve this?
Sorry figured it out, instead of x:Name use x:Key as below:
<Window.Resources>
<Style x:Key="FocusMode" TargetType="RichTextBox">
<Setter Property="VerticalScrollBarVisibility" Value="Disabled"></Setter>
</Style>
<Style x:Key="NormalMode" TargetType="RichTextBox">
<Setter Property="VerticalScrollBarVisibility" Value="Auto"></Setter>
</Style>
I'd have a look at style triggers. You can probably get a good start on the subject from this post: How to make Style.Triggers trigger a different named style to be applied

How to give my custom control an overridable default margin?

Problem
I've created a custom control (OmniBox), which has its base style set with:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="0,2" />
</Style>
But when I'm using my control, I want to be able to do something like:
<UserControl.Resources>
<Style TargetType="{x:Type ui:OmniBox}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,10"/> <!--Not Working?-->
</Style>
</UserControl.Resources>
<Grid>
<StackPanel>
<ui:OmniBox x:Name="One"... />
<ui:OmniBox x:Name="Two"... />
...
And have all instances of my control take on that default margin. Unfortunately, my controls are not responding to the style set in the resources. They are just keeping their default margin of "0,2".
Strangely, if I explicitly set the margin on my controls like so:
<ui:OmniBox x:Name="One" Margin="0,10" Style="OBDefaultStyle" ... />
<ui:OmniBox x:Name="Two" Margin="0,10" ... />
...
They DO use the margin of "0,10" rather than "0,2". How come the template type isn't working?
If it's relevant, my OmniBox control templates all look like this:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
<ControlTemplate TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultTemplate">
<Grid x:Name="PART_Grid" Style="{StaticResource GridStyle}">
... (Content)
</Grid>
</ControlTemplate>
First Attempt
In my grid style, I've tried setting Margin to
<Setter Property="Margin"
Value="{Binding Path=Margin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OmniBox}}}" />
But it didn't help in sucking down the templated margin.
Second Attempt
I tried creating a custom margin dependency property and binding the grid to that:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="{Binding Path=MyMargin, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
My custom property was defined as:
public static readonly DependencyProperty MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2), new PropertyChangedCallback(OnMarginChanged)));
Anyways it didn't work. The default margin set in the dependency property above is still overriding the margin I'm trying to set in the style template.
You can add a default style for a custom control by overriding the metadata for the DefaultStyleKey:
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
You then create a resource dictionary called Generic.xaml that is located in a directory called Themes in the root of the project (so the path will be "/Themes/Generic.xaml"). In that resource dictionary you create a default style for your control:
<!-- Base the style on the default style of the base class, if you don't want to completely
replace that style. If you do, remember to specify a new control template in your style as well -->
<Style TargetType="SomeNamespace:MyButton" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="10" />
</Style>
If you just add a MyButton control it will get the default style, but you can override properties set in the default style by applying a new style:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SomeNamespace="clr-namespace:SomeNamespace"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="SomeNamespace:MyButton">
<Setter Property="Margin" Value="20" />
</Style>
</Window.Resources>
<Grid>
<SomeNamespace:MyButton />
</Grid>
</Window>
GridStyle specifies TargetType="Grid", so the setter <Setter Property="Margin" Value="0,2" /> applies to the Grid at the root of the control template. Setting the Margin property of the containing OmniBox has no effect of the margin of that grid.
Try specifying this in the template:
<Grid x:Name="PART_Grid" Margin="{TemplateBinding Margin}">
Notice I did not set the Style property as you did in the template. This is because the grid's Margin property will always reflect the Margin property of the OmniBox containing it, negating the effect of the Margin property in GridStyle. Instead you will want to default the OmniBox.Margin property and remove GridStyle entirely:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Margin" Value="0 2" />
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
Have you overridden the DefaultStyleKey property in your OmniBox control?
After happening on this question, I figured out what I needed to do. In the control's class, I need to override the margin property's default value:
static OmniBox()
{
MarginProperty.OverrideMetadata(typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2)));
}
After that, I get rid of the margin on the "Grid" component of the omnibox completely, since the control itself carries a margin. Now when the user sets the "Margin" property on the OmniBox, it accepts it, if they don't, it uses the default value.
Thank you all so much for your suggestions and effort.

Categories

Resources