【问题标题】:Override property of custom style覆盖自定义样式的属性
【发布时间】:2018-05-08 07:42:15
【问题描述】:

我的样式适用于我的应用程序的所有按钮:

<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Background" Value="Red" />
    <Setter Property="Foreground" Value="Black" />
    <Setter Property="FontSize" Value="16" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Ellipse x:Name="StatusButtonCircle" Stroke="Black" StrokeThickness="0" Fill="AliceBlue" Stretch="Uniform">
                        <Ellipse.Width>
                            <Binding ElementName="StatusButtonCircle" Path="ActualHeight"/>
                        </Ellipse.Width>
                    </Ellipse>
                    <Ellipse x:Name="StatusButtonCircleHighlight" Margin="4" Stroke="Black" StrokeThickness="2" Stretch="Uniform">
                        <Ellipse.Width>
                            <Binding ElementName="StatusButtonCircleHighlight" Path="ActualHeight"/>
                        </Ellipse.Width>
                    </Ellipse>
                    <ContentPresenter HorizontalAlignment="Center"  
                                    VerticalAlignment="Center"/>
                </Grid>

                <ControlTemplate.Triggers>
                    ... some Triggers here
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

如何更改 XAML 中的属性(例如 FontWeight、FontSize 等)?我试过这个:

<Button FontWeight="Bold" FontSize="30" Foreground="Red">
</Button>

在设计器视图中,我看到了变化。但在运行时这些更改不会应用。


经过一番调查,我也有一个适合所有 TextBlock 的样式,如下所示:

<Style TargetType="{x:Type TextBlock}">
    <Setter Property="FontSize" Value="16" />
    <Setter Property="FontFamily" Value="Segoe UI Semibold" />
    <Setter Property="Foreground" Value="White" />
</Style>

此样式似乎覆盖了按钮上使用的 TextBlock。我仍然无法更改 XAML 中的文本属性。

如果我在一个空项目中使用上述样式,效果如下:

在设计器中应用更改,在运行时应用来自 TextBlock 的更改。如果我为 TextBlock 分配一个 x:Key,它就可以正常工作。但是我必须手动将此样式分配给应用程序中使用的每个 TextBlock。

【问题讨论】:

  • 您提供的代码不会重现该问题。我刚刚创建了新项目,将 Button 和 TextBlock 样式添加到资源中,添加了 Button 元素,并且它可以工作(前景为红色)。尝试创建一个可以重现问题的测试项目。
  • BasedOn 应该指向另一个Button的“base”样式,它不用于指定目标类型。
  • @Fredrik 当你想扩展类型的全局样式时可以这样使用 - microsoft docs。
  • @djomlastic 是的。谢谢。

标签: wpf xaml button properties


【解决方案1】:

您在 wpf 中面临典型的样式继承问题。

控件在初始化时查找其样式。控件查找样式的方式是在逻辑树中向上移动并询问逻辑父级是否有合适的样式存储在父级的资源字典中。

在您的情况下,您在按钮中使用 ContentPresenter 作为默认行为。它默认使用TextBlock来表示按钮中的文本。

因此在初始化时,ContentPresenter 会找到 TextBlock 的样式,并申请在按钮中表示内容。

如果您想限制 ContentPresenter 查找样式,则必须将空白样式绑定到内容演示者,以便它不会查找任何其他样式。

<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Background" Value="Red" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="FontSize" Value="16" />

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse x:Name="StatusButtonCircle" Stroke="Black" StrokeThickness="0" Fill="AliceBlue" Stretch="Uniform">
                                <Ellipse.Width>
                                    <Binding ElementName="StatusButtonCircle" Path="ActualHeight"/>
                                </Ellipse.Width>
                            </Ellipse>
                            <Ellipse x:Name="StatusButtonCircleHighlight" Margin="4" Stroke="Black" StrokeThickness="2" Stretch="Uniform">
                                <Ellipse.Width>
                                    <Binding ElementName="StatusButtonCircleHighlight" Path="ActualHeight"/>
                                </Ellipse.Width>
                            </Ellipse>
                            <ContentPresenter HorizontalAlignment="Center"  
                                    VerticalAlignment="Center">
                                <ContentPresenter.Resources>
                                    <Style TargetType="TextBlock" BasedOn="{x:Null}"/>
                                    <!--  Assigned Blank style here therefore it will not search for any further style-->
                                </ContentPresenter.Resources>
                            </ContentPresenter>
                        </Grid>


                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

【讨论】:

    【解决方案2】:

    您可以使用BasedOn 来完成。我给你举个例子。

    <Window.Resources>
        <Style TargetType="ToggleButton" BasedOn="{StaticResource DefToggleButton}">
           <Setter Property="FontWeight" Value="Bold"/>
           <Setter Property="Content" Value="Some Cool Stuff"/>
               <Style.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                          <Setter Property="Content" Value="More Stuff"/>
                    </Trigger>
               </Style.Triggers>
        </Style>
    </Window.Resources>
    

    在我的资源中,我有 DefToggleButton,现在在我的 xaml 文件中,我可以根据需要设置任何 Property(在本例中为 FontWeight 和 Content Property)。

    【讨论】:

      【解决方案3】:

      我认为如果你从你的Style 中删除Template,那么你可以做你想做的事,就像这样:

      <Window.Resources>
          <Style TargetType="Button" x:Key="stBtn>
              <Setter Property="Background" Value="Blue" />
              <Setter Property="Foreground" Value="White" />
              <Setter Property="FontFamily" Value="Segoe UI Semibold" />
          </Style>
      </Window.Resources>
      

      你所说的Template 说,所有Buttons 都应该显示为Border,里面有ContentPresenter,这不是你问的。

      没有Template,您可以像这样定义您的Buttons:

      <Button Content="Hi!" Style="{StaticResource stBtn}" Foreground="Red" >
      

      像这样,您有一个蓝色的Button,前景为红色。

      ==================

      编辑

      如果你定义一个Template,并在你的风格中使用它,像这样呢?

      然后,通过TemplateBinding,您可以定义 Foreground 和 teh Content 在实际定义 Button 时稍后出现。

      <Window.Resources>
          <ControlTemplate x:Key="ctBtn" TargetType="{x:Type Button}">
              <Label Background="Green" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}"/>
          </ControlTemplate>
      
          <Style x:Key="stBtn2" TargetType="{x:Type Button}">
              <Setter Property="Template"
                  Value="{StaticResource ctBtn}" />
          </Style> 
      <Window.Resources>
      

      然后通过定义Button:

      <Button Content="Hi!" Style="{StaticResource stBtn2}" Foreground="Red" >
      

      ================

      编辑2

      所以总体思路是,您可以为模板中元素的属性定义一个 TemplateBinding。例如,您的模板中有一个椭圆:

      <Ellipse Fill="{TemplateBinding BorderBrush}" /> 
      

      这定义了 Ellipse 的 Fill 属性来自 Button 的 BorderBrush(假设模板针对的是 Button)

      因此,您可以在您的Template 中放置一个Label,并为其Forground 和FontWeight 属性设置一个TemplateBinding。

      <Label Foreground="{TemplateBinding Foreground}" />   
      

      【讨论】:

      • 我简化了问题中的样式。我确实需要模板,我添加了一个带有椭圆等的网格。
      • @el-nino 编辑了答案。
      • 抱歉,我稍后在您的问题中看到了编辑后的模板。所以总体思路是,您可以为模板中元素的属性定义一个TemplateBinding。例如:您的模板中有一个椭圆。 &lt;Ellipse Fill="{TemplateBinding BorderBrush}" /&gt; 这定义了 Ellipse 的 Fill 属性来自 Button 的 BorderBrush(假设模板针对的是 Button)
      【解决方案4】:

      首先,要复制此问题,Styles 需要设置在 ResourceDictionary 中,然后将其添加到 Application.Resources(精确为TextBlock 全局样式)。在例如Window.Resources 中设置Styles 不会重现该问题。

      全局文本块样式应用于 ConentPresenter 创建的文本块

      正如问题中所注意到的,问题在于TextBlock 的全局(无键)Style 应用于ContentPresenter 创建的TextBlock,当它断定要显示的内容是字符串时。出于某种原因,当Style 在Window.Resources 中定义时,这不会发生。事实证明,这不仅仅是“控件在其父资源中寻找样式”。

      ControlTemplate 是不是从 Control 类派生的元素的边界

      对于ControlTemplate 内的TextBlock(不是从Control 类派生,而是从UIElement),这意味着wpf 不会寻找它的隐含Style 以外 它是模板化的父级。因此它不会在其父资源中查找隐式 Style,它会应用在 Application.Resources 中找到的应用程序级别隐式 Style。

      这是设计使然(如果愿意,可以硬编码为 FrameworkElement),原因正是为了防止出现此类问题。假设您正在创建一个特定的Button 设计(就像您一样),并且您希望应用程序中的所有按钮都使用该设计,甚至是其他ControlTemplates 中的按钮。好吧,他们可以,因为Button 确实源自Control。另一方面,您不希望所有使用TextBlock 来呈现文本的控件都应用隐式TextBlock Style。 ComboBox、Label... 也会遇到同样的问题,因为它们都使用 TextBlock,而不仅仅是 Button。

      所以结论是:不要为不是从Application.Resources 中的Control 类派生的元素定义全局Style,除非你100% 确定这是你想要的 (例如,将其移至Window.Resources)。或者,引用我在MahApps.Metro UI 库的源代码中找到的评论:“永远不要在 App.xaml 中为 TextBlock 设置默认样式!!!”。您可以使用一些解决方案在您的Button 的ControlTemplate 中设置TextBlock 的样式,但是您必须为Label、ComboBox 等设置样式...所以,不要.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-18
        • 2017-12-23
        • 2019-10-31
        • 1970-01-01
        • 2014-04-14
        • 1970-01-01
        • 2013-06-10
        • 1970-01-01
        相关资源
        最近更新 更多