How to give my custom control an overridable default margin? - c#

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.

Related

How to avoid childrens of groupbox taking there default styles which are defined in resource or resourcedictionary in wpf

Style defined in Resource
<Setter Property="Background" Value="Red"/>
</Style>enter code here
</Window.Resources>
In window i am adding a groupbox with child label .
<Grid>
<GroupBox Header="Header">
<GroupBox.Resources>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="white"/>
</Style>
</GroupBox.Resources>
<Label Content="dsfdsfdsf" Foreground="Black" />
</GroupBox>
</Grid>
My Expected Result was Label taking background of white . But actually it is taking Red Background (that is defined in style of Resource )
If i set the style of the Label to explicitly null it works fine
Label Content="dsfdsfdsf" Foreground="Black" Style={x:Null}
But Controls to GroupBox are dynamically added so i want to set
Style={x:Null} to all childrens that are being added to Group box
if i set OverrideDefalutStyle to True in Label the content of label is not comming ......................
Label Content="dsfdsfdsf" Foreground="Black" OverridesDefaultStyle="True"
That style in GroupBox.Resources has no effect on the GroupBox itself. The implicit GroupBox's style is the one of its closest ancestor on VisualTree. You put that style in the wrong place.
Or use Style property instead
<Grid>
<GroupBox Header="Header">
<GroupBox.Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="white"/>
</Style>
</GroupBox.Style>
<Label Content="dsfdsfdsf" Foreground="Black" />
</GroupBox>
</Grid>
By setting
<Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="White"/>
</Style>
you will set the background of all GroupBox controls within your GroupBox and the GroupBox itself to white.
So if you want to set/override the Background of all Labels within your GroupBox just add an additional Style to your GroupBox targeting Label
<Style TargetType="{x:Type Label}">
<Setter Property="Background" Value="White"/>
</Style>
If you want to reset the style property of your Label just add an empty style definition to your GroupBox
<Style TargetType="{x:Type Label}"/>
The next approach is used on your on risk :)
If you only want to reset the background color, you can do this trick/hack to reset:
<Style TargetType="{x:Type Label}">
<Setter Property="Background" Value="{Binding Background.DefaultValue, RelativeSource={RelativeSource Self}}" />
<Setter Property="Foreground" Value="Black" />
</Style>
Hint: Instead of Background.DefaultValue you can also write Background.ABC the main thing here is that the binding goes wrong.

Style not being applied to inner element via Style property binding

I have got a custom control composed of many parts.
One of those parts is a Border.
I need to style that border from outside the control so i created a dependency property of type Style and bound it to the Border like this:
<ControlTemplate TargetType="{x:Type cc:DrawingLayer}" >
...
<Grid x:Name="grid" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" >
<Border x:Name="PART_AreaSelector" Style="{Binding AreaSelectorStyle}" BorderBrush="#FF3399FF" BorderThickness="1" Background="#55ADD8E6" />
</Grid>
</ControlTemplate>
In the window where i use the control i try to define its style this way:
<cc:DrawingLayer.AreaSelectorStyle>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="10" />
<Setter Property="Background" Value="Red"/>
</Style>
</cc:DrawingLayer.AreaSelectorStyle>
But it seems that the style is not applied. No property reflects the values in the style.
Can someone point out what i'm missing?
The "local" property values assigned in
<Border ... BorderBrush="#FF3399FF" BorderThickness="1" Background="#55ADD8E6" />
have higher precendence than the values set by Style Setters.
You need to set a default Style for those values.
See Dependency Property Value Precedence on MSDN.

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" />

Apply a WPF style to a Border within a UserControl

I have a very simple UserControl, which just consists of a TextBlock contained within a Border element.
Is there a way of applying a style to the TextBlock within the UserControl from the containing window.
I know that I can create a style with..
<Style TargetType='TextBlock'>
But this applies to all TextBlocks within my window, not just the ones inside my UserControl
So I want to be able to say something like...
<Style TargetType='MyUserControl.TextBlock'>
Thanks,
Rich.
PS. this is a simplified example of what I'm trying to do!
ADDITIONAL NOTE
As I was driving home this evening, this was rattling around inside my head, and I thought of a possible solution.. and that's to create a basic sub-class of the TextBlock control, and call it MyTextBlock.. so just have a definition like
public class MyTextBlock : TextBlock { }
Then, within the usercontrol, use 'MyTextBlock' rather than 'TextBlock'. This will allow me to apply a style to a type of 'MyTextBlock'. Bingo !!!
Maybe this isn't the tidiest way of doing this, but it's very little code, and it works.
However, as I'm fairly new to WPF, I'm quite interested in a more standard way of acheiving this.
One option if you want to apply a Style to all TextBlocks inside MyUserControl is this
<Style TargetType="{x:Type my:MyUserControl}">
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</Style.Resources>
</Style>
And if you want to add another Style for MyUserControl you just have to base it on the default Style
<Style x:Key="myStyle" TargetType="{x:Type my:MyUserControl}"
BasedOn="{StaticResource {x:Type my:MyUserControl}}">
<!-- ... -->
</Style>
Otherwise, if you want to be able to set a Style for some controls inside MyUserControl you can use DependencyProperties. For a TextBlock you can have a style called TextBlockStyle for example. The TextBlock will bind to this Style and you can set the Style from your window (or wherever you use it). This can also be seen in some controls in the framework, AutoCompleteBox in the toolkit for example
<toolkit:AutoCompleteBox>
<toolkit:AutoCompleteBox.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="MaxLength" Value="10"/>
</Style>
</toolkit:AutoCompleteBox.TextBoxStyle>
</toolkit:AutoCompleteBox>
MyUserControl.xaml
<Border BorderThickness="1">
<TextBlock Style="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=TextBlockStyle}"
Text="Test"/>
</Border>
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl
{
public static DependencyProperty TextBlockStyleProperty =
DependencyProperty.Register("TextBlockStyle",
typeof(Style),
typeof(MyUserControl));
public MyUserControl()
{
InitializeComponent();
}
public Style TextBlockStyle
{
get { return (Style)GetValue(TextBlockStyleProperty); }
set { SetValue(TextBlockStyleProperty, value); }
}
}
And then you set the Style when you declare your instance in markup for example
<my:MyUserControl>
<my:MyUserControl.TextBlockStyle>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Green"/>
</Style>
</my:MyUserControl.TextBlockStyle>
</my:MyUserControl>
Update
To set this on resource level you can add a default style for MyUserControl, in the window resources or App.xaml for example
<Window.Resources>
<Style TargetType="{x:Type my:MyUserControl}">
<Setter Property="TextBlockStyle">
<Setter.Value>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Green"/>
</Style>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
There are a few things you can change.
First, you can use Styles with a resource key. So you would write:
<Style x:Key="myTextStyle" TargetType="{x:Type TextBlock">
Then in order for a TextBox to have this style applied to it, it would need to specify:
<TextBlock Style="{StaticResource myTextStyle" />
If you do not want to modify the UserControl and you just want to apply the style in an element that is nesting the UserControl inside it, keep in mind that you can declare styles in the resource dictionary of a nested element. When you place your UserControl, consider doing this:
<local:UserControl>
<local:UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
...
</Style>
</local:UserControl.Resources>
</local:UserControl>
There are 2 ways I can think of doing this:
One is in code in the containing window by explicitly setting the style
MyUserControl.TextBlockName.Style = FindResource("TextBlockStyle") as Style;
The other way would be to create a DependencyProperty to hold the Textblock style and apply the style on the TextBlock element when the DependencyProperty is changed.
if you have something like this in your xaml:
<xmlns:local = "myCustonUserControl">
you can use this as a style definition:
<Style TargetType="{x:Type local:MyUserControl}">
hope that helps!

Unset a value in a custom Style?

I my WPF application I'd like to change the text color by setting Foreground on the main Window like
<Window Foreground="Red">
<TextBlock Text="Hello World" />
</Window>
This works fine for TextBlocks, but if I add a Button, the font there stays black, since Button has a Setter for Foreground in its default style. can I make a new default style for Button based on the original one, but removing the Foregound setter?
I haven't tested this, but you could try using a base style in an appropriate resource collection (e.g. at the application level in App.xaml), and then create an implicit style for each type of control based on this base style.
<Style TargetType="{x:Type Control}" x:Key="DefaultControlStyle">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource DefaultControlStyle}" />
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource DefaultControlStyle}" />
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultControlStyle}" />
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource DefaultControlStyle}" />
You could also use a relative source binding trick described here.

Categories

Resources