如何在WPF中设置特定的控件元素设置,以覆盖全局样式?

4

我通过应用程序范围内的资源字典来定义了按钮的全局样式。 样式如下所示(从另一个SO示例中复制):

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

它起作用了。但我需要直接为一些按钮分配特定的值,比如边距和填充以对齐它们。我也想要能够覆盖单个按钮中样式的颜色属性。
看起来,我在特定按钮上设置的任何属性都被完全忽略,只使用全局样式中的属性。我该如何解决这个问题?
更新:为了澄清我的需求:在HTML/CSS世界(比土还古老),你可以为元素添加样式类,但你也可以直接为元素分配属性,覆盖类的值。这就是我想在WPF中实现的功能。
更新2:可能有人认为这个问题很愚蠢,因为解决方案应该很明显。然而,根据我的个人测试,似乎存在一个Padding不改变的bug,除非你在控件模板中明确地绑定它。这种行为似乎会从属性到属性发生变化。由于我最初尝试覆盖一个具体属性涉及到Padding并且它不起作用,所以我不得不构建这个解决方法。

我添加了一个额外的示例,演示如何在运行时更改“Margin”属性。 - Zam
2个回答

3
是的,这是完全可行的。您可以直接在元素上分配覆盖属性,而不必像许多人一样创建特定元素的一次性字典条目这样的丑陋过程。
我不知道是否由于WPF中的错误,但存在一个最初的要求...
您的字典引用基本样式可能需要包括您想要覆盖的任何属性。由于某种原因,不同的属性似乎表现出不同的行为。但至少在填充的情况下,如果您没有在ControlTemplate TemplateBinding上包含填充,则无法在元素上覆盖它。
此外,在边距的情况下,似乎会发生某种“加倍”效果,如果在ControlTemplate TemplateBinding中包含Margin,则可以仍然覆盖边距,但行为会发生变化。 步骤1 定义一个具有ControlTemplate的基本样式。确保您的ControlTemplate包括所有可能要在单个元素上自定义/覆盖的属性的TemplateBinding。
<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>

我已经定义了一些静态资源键,用于我的属性颜色,以便我可以将它们放在另一个地方以获得更清晰的皮肤。以下是这些键:

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

第二步
实现如下所示的实际按钮:

STEP 2

实现实际按钮,如下所示:

<Button Content="New" />

这样做的结果是一个看起来像这样的按钮:

Initial Button Style

步骤 3

现在假设我想让所有按钮看起来都像那样被压扁,除了一个。 我想添加一些垂直填充以使特定的一个按钮看起来更高。 我可以像这样修改示例按钮:

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

并且结果是:

Altered Button Style

或者,您可以按照以下方式实现按钮覆盖,从而使您能够覆盖触发器或其他特殊样式选项。

<Button Content="New">
    <Button.Style >
        <Style TargetType="Button" BasedOn="{DynamicResource {x:Type Button}}">
            <Setter Property="Padding" Value="0,30"/>
        </Style>
    </Button.Style>
</Button>
哔——!我们已经直接将单次样式调整分配给了它所属的元素!我们不需要为这种情况创建一个字典中的样式并引用它。

重要提示

为了使其工作,“填充”必须在ControlTemplate中使用TemplateBinding代码进行定义。如果您不这样做,直接应用于按钮的填充将被完全忽略。再次强调,我不确定为什么会这样,但这似乎是一个神奇的解决方法。

进一步阅读:我能够从这篇博客文章中获得一些有用的信息来解决这个问题:

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

有趣的是,该文章的最后一部分建议创建具有自己默认样式和属性的自定义控件,您可以类似于我在这里所做的方式对其进行覆盖。这对我来说似乎有点过度,但它可能消除填充和边距行为不同的奇怪错误问题。尽管如此,我还没有尝试过。


这个 XAML <Style TargetType="Button" BasedOn="{DynamicResource {x:Type Button}}"> 会导致错误:"不能在 'Style' 类型的 'BasedOn' 属性上设置 'DynamicResourceExtension'。'DynamicResourceExtension' 只能在 DependencyObject 的 DependencyProperty 上设置。" 改为 StaticResource 会导致基本样式不被应用。对此感到困惑,希望这是解决方案。可能会就此问题提交一个新的问题。 - wopr_xl

1

好的,在设计中的表单:

enter image description here

表单的XAML代码:

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

在运行时,我们将使用以下代码在 CS 文件中更改 Margin

public MainWindow()
{
    InitializeComponent();

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

Result will be:

enter image description here



你能否创建并使用新的按钮样式,以便在需要自定义边距/填充时使用?
<Style x:Key="SpecialButtonType1" BasedOn="{StaticResource ResourceKey=CommonButtonStyle}">
...
</Style>

和改变

<Style TargetType="Button">

<Style TargetType="Button" x:Key="CommonButtonStyle">

为一个一次性元素创建自定义样式是冗长而痛苦的,我应该能够直接将属性分配给元素。这是WPF的错误吗?在HTML/CSS世界(比土还古老),您可以向元素添加样式类,但也可以直接将属性分配给元素以覆盖类值。这就是我想在WPF中实现的。 - JamesHoux
是的,WPF中的样式与CSS不同:据我所知,您不能为一个控件/组件设置几个样式。 - Zam
如果这是真的,那么这个架构设计非常糟糕。你不能完全将内容与设计分离,因为内容通常必须根据其所显示的周围环境进行调整。在HTML世界中,经常需要微调单个元素以获得最终的正确外观。HTML之所以允许这样做,是因为它是布局设计中的基本必需品。如果在WPF中没有优雅地完成这项工作的方法,我已经准备好放弃WPF了。 :P - JamesHoux
需要转到代码后台非常笨重。布局调整需要在控件本身上完成,以便可维护性。 - JamesHoux
@JamesHoux 对不起,我错了。你可以通过Ad-Hoc应用几种样式。请查看以下链接:https://dev59.com/33VD5IYBdhLWcg3wVJ8b - Zam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接