4

I have defined a global style for Buttons in my application via an application-wide Resource Dictionary. The style looks like this (followed from another SO Example):

<Style TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{DynamicResource BaseButtonBG}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Button}">
            <Grid Background="{TemplateBinding Background}">
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>
<Style.Triggers>
    <!-- Triggers here -->>
</Style.Triggers>

It works. But I need to assign specific values directly to some of my buttons, like margin and padding to align them. I also would like to have the ability to override the color properties from the style in individual buttons.

It appears that any properties I set directly on specific buttons get completely ignored and only the properties from the global style are used. How do I overcome this?

UPDATE: To clarify what I want: In the HTML/CSS world (which is older than dirt), you can add a style class to an element, but you can also assign properties directly to the element that override the class values. That's what I want to accomplish in WPF.

UPDATE 2 It's possible people think this question is stupid because the solution should be obvious. However, from my personal testing, there appears to be a bug with Padding not changing at all unless you specifically bind it in a control template. This behavior seems to change from property to property. Since my original attempt to override a property specifically involved Padding and it didn't work, I had to build this workaround.

JamesHoux
  • 2,999
  • 3
  • 32
  • 50

2 Answers2

3

YES: It's completely doable, You can assign overriding properties directly on an element without doing the ugly process many are using of creating a special one-off dictionary entry just for the specific element in question.

I don't know if its caused by a bug in WPF, but there's an initial requirement...

Your dictionary-referenced base style might need to include any properties that you want to be overridable. For some reason different properties seem to exhibit different behavior. But at least in the case of Padding, if you don't include Padding on your ControlTemplate TemplateBinding, you won't be able to override it on your element.

Additionally, in the case of margin, there seems to be some kind of "doubling" effect that happens if you include Margin in the ControlTemplate TemplateBinding. If you don't templateblind the margin, you can still override margin but the behavior changes.

STEP 1

Define a base style with a ControlTemplate. Make sure that your ControlTemplate includes a TemplateBinding for all properties that you may want to customize/override on individual elements.

<Style TargetType="Button">
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="Background" Value="{StaticResource BaseButtonBG}"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border 
                    Background="{TemplateBinding Background}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}"
                    Margin="{TemplateBinding Margin}"
                    >
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="{StaticResource BaseButtonBG_IsMouseOver}"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Background" Value="{StaticResource BaseButtonBG_IsPressed}"/>
        </Trigger>
    </Style.Triggers>
</Style>

I've defined a few StaticResource keys for my property colors so that I can put them altogether in another place for cleaner skinning. Here are those keys:

<SolidColorBrush x:Key="BaseButtonBG"                   Color="#5f636c"/>
<SolidColorBrush x:Key="BaseButtonBG_IsMouseOver"       Color="#898C94"/>
<SolidColorBrush x:Key="BaseButtonBG_IsPressed"         Color="#484B51"/>

STEP 2

Implement actual button like this:

<Button Content="New" />

And the result of this makes a button that looks like this:

Initial Button Style

STEP 3

Now let's say I want all of my buttons to look squashed like that, except one. I want to add some vertical padding to make one specific button look taller. I could alter the example button like this:

<Button Content="New" Padding="0,30"/>

And the result:

Altered Button Style

Alternatively, you could implement the button override as follows, which gives you the ability to override Triggers or other special Style options.

<Button Content="New">
    <Button.Style >
        <Style TargetType="Button" BasedOn="{DynamicResource {x:Type Button}}">
            <Setter Property="Padding" Value="0,30"/>
        </Style>
    </Button.Style>
</Button>

TADA! We've assigned a one-off style tweak directly to the element WHERE IT BELONGS! We didn't have to create a style in a dictionary and reference it for this one case.

Important Points

In order to make this work "Padding" MUST BE defined in the ControlTemplate with the TemplateBinding code. If you don't do that, Padding directly applied to the button just gets ignored completely. Again, I'm not sure why its like this, but that seems to be the magic fix.

Further Reading: I was able to figure this out from some helpful info on this blog article:

explicit-implicit-and-default-styles-in-wpf

Interestingly, the last part of that article suggests creating a custom control with your own default style and properties that you can override similarly to how I've done here. This seems a bit overkill to me, but it might eliminate the weird bugginess problem with padding and margin behaving differently. Haven't tried that yet, though.

JamesHoux
  • 2,999
  • 3
  • 32
  • 50
  • This XAML ` – wopr_xl Mar 10 '23 at 15:15
1

Ok, form in Design:

enter image description here

XAML code for form:

<Window.Resources>

    <Style TargetType="Button">
        <Setter Property="Foreground" Value="Red"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid Background="{TemplateBinding Background}">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>


<Grid>
    <Button Content="Button" x:Name="btnNo1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="120"/>
    <Button Content="Button" x:Name="btnNo2" HorizontalAlignment="Left" Margin="135,10,0,0" VerticalAlignment="Top" Width="120"/>

</Grid>

and in RunTime we going to change Margin by using this code in CS file:

public MainWindow()
{
    InitializeComponent();

    btnNo2.Margin = new Thickness(100, 100, 100, 100);
}

Result will be:

enter image description here



Can you create and use new style for button where you need custom margin/padding?

<Style x:Key="SpecialButtonType1" BasedOn="{StaticResource ResourceKey=CommonButtonStyle}">
...
</Style>

and change

<Style TargetType="Button">

to

<Style TargetType="Button" x:Key="CommonButtonStyle">
Zam
  • 2,880
  • 1
  • 18
  • 33
  • Having to create a custom style for a one-off element is verbose and painful, when I should be able to just assign properties directly to the element. Is this a fault with WPF? In the HTML/CSS world (which is older than dirt), you can add a style class to an element, but you can also assign properties directly to the element that override the class values. That's what I want to accomplish in WPF. – JamesHoux Feb 02 '19 at 18:21
  • Yep, styles in WPF are different than CSS: AFAIK you cannot have few styles for one control/component. – Zam Feb 02 '19 at 18:44
  • If this is true, this is disturbingly ill-designed architecture. You can't separate content from design completely, because the content must often by adjusted based on the surrounding environment in which it is displayed. In the HTML world, it's extremely common to need to tweak individual pieces to get the final look right. HTML allows this because it's a fundamental necessity in layout design. If there's no way to do this elegantly in WPF, I'm already prepared to renounce WPF. :P – JamesHoux Feb 02 '19 at 20:08
  • Having to go to the code-behind is extremely clunky. The layout adjustments need to be done on the control itself where its declared in order to be maintainable. – JamesHoux Feb 02 '19 at 20:11
  • @JamesHoux I am sorry, i was wrong. You could apply few styles, but via Ad-Hoc. PLease take a look into: https://stackoverflow.com/questions/16096/how-to-apply-multiple-styles-in-wpf – Zam Feb 02 '19 at 20:16