设置整个窗口的前景色

29

我想将所有元素的前景(文本)颜色设置为特定的颜色。

<Window Foreground="Red">
   <Label Content="Test"/>
   <Label Content="Test"/>
   <CheckBox Content="Checkbox"/>
</Window>

这没有任何效果...唯一能让它起作用的方法是在每个元素上单独设置Foreground属性。如果有数百个元素,等等,这将非常麻烦。

也许你知道其他方法?

4个回答

34

这是因为像LabelCheckBox这样的控件在它们的样式中覆盖了Foreground属性。

下面是一个典型逻辑元素树的示例,显示了在树中从Window级别指定的值如何向下传递:

Window (Red [Local]) 
  -> Grid (Red [Inherited]) 
     -> ListBox (Red [Inherited]) 
        -> ListBoxItem (Red [Inherited]) 
           -> StackPanel (Red [Inherited]) 
              -> Label (Black [Style])
                 -> TextBlock (Black [Inherited])
              -> TextBlock (Red [Inherited])

在方括号中显示值的来源。

如您所见,继承关系在 Label 上被打破,因为它具有默认样式中设置的 Foreground 属性:

<Style x:Key="{x:Type Label}"
       TargetType="{x:Type Label}">
    <Setter Property="Foreground"
            Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    ...
</Style>

为了解决这个问题,我们可以使用以下方法。在应用程序中(在 App.xaml 或 Window 中)定义这些控件(如 Label)的默认样式。然后在该默认样式中通过覆盖 Foreground 属性来设置一个相对源绑定,以便将其设置为仍具有所需值的控件的最近祖先:

<Style TargetType="{x:Type Label}">
    <Setter Property="Foreground"
            Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}"/>
</Style>

<Style TargetType="{x:Type CheckBox}">
    <Setter Property="Foreground"
            Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}"/>
</Style>

之后我们的树将会是这个样子:

Window (Red [Local]) 
  -> Grid (Red [Inherited]) 
     -> ListBox (Red [Inherited]) 
        -> ListBoxItem (Red [Inherited]) 
           -> StackPanel (Red [Inherited]) 
              -> Label (Red [Binding to StackPanel.(TextElement.Foreground)])
                 -> TextBlock (Red [Inherited])
              -> TextBlock (Red [Inherited])

正如您所看到的,我们的绑定恢复了继承。

这些样式需要为每个在其样式中覆盖Foreground属性的元素定义。正如@Duane建议的那样,为了不在每个样式中重复绑定,可以使用BasedOn功能:

<Style x:Key="ForegroundInheritanceFixStyle"
       TargetType="Control">
    <Setter Property="Foreground"
            Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}"/>
</Style>

<Style TargetType="{x:Type Label}"
       BasedOn="{StaticResource ForegroundInheritanceFixStyle}">
</Style>

<Style TargetType="{x:Type CheckBox}"
       BasedOn="{StaticResource ForegroundInheritanceFixStyle}">
</Style>
希望这可以帮到您。

当您在可视树中只有一个父级时,这将足够好用,但通常标签和复选框会嵌套在更深的层次结构中。 - Dabblernl
1
@Dabblernl - 不,这将始终有效。它唯一做的是恢复由元素默认样式上的样式设置器破坏的继承。例如,如果您有像这样的树:Window -> Grid -> ListBox -> ListBoxItem -> StackPanel -> Label。如果您在Window上设置Foreground属性,则该值将通过继承传递给Label,如下所示:Window(红色)-> Grid(红色[继承])-> ListBox(红色[继承])-> ListBoxItem(红色[继承])-> StackPanel(红色[继承])-> Label(红色[绑定到StackPanel.(TextElement.Foreground)])。 - Pavlo Glazkov
虽然这通常有效,但我在使用Frames时遇到了一些问题,似乎会破坏继承。System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.FrameworkElement', AncestorLevel='1''. BindingExpression:Path=(0); DataItem=null; target element is 'Border' (Name=''); target property is 'Foreground' (type 'Brush') - ANeves
我通过将模板设置为默认样式中的框架,并在模板中不包括边框来解决了我的问题。 - ANeves

6

遗憾的是,在WPF中,样式的工作方式不允许您在父类上创建通用样式,并将其应用于子类控件。

您可以做的一件事是创建一个基础样式,针对具有您要设置属性的基础类型(如ContentControl),然后为每个控件创建一个特定样式,该样式基于该样式。以下是一个示例:

<Window>
    <Window.Resources>

        <Style x:Key="BaseContentControlStyle" TargetType="{x:Type ContentControl}">
            <Setter Property="Foreground" Value="Red" />
        </Style>

        <Style TargetType="{x:Type Label}" BasedOn="{StaticResource BaseContentControlStyle}" />

        <Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource BaseContentControlStyle}" />

    </Window.Resources>

    <StackPanel>
        <Label Content="Test"/>
        <Label Content="Test"/>
        <CheckBox Content="Checkbox"/>
    </StackPanel>   
</Window>

希望这能帮到你。
编辑:
您可以使用Pavlo下面的方法来恢复继承,并使其更容易使用,如下所示:
<Window.Resources>

    <Style x:Key="BaseContentControlStyle" TargetType="{x:Type Control}">
        <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}"/>
    </Style>

    <Style TargetType="{x:Type Label}" BasedOn="{StaticResource BaseContentControlStyle}" />

    <Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource BaseContentControlStyle}" />

</Window.Resources>

至少这样你就不必在各处复制相同的setter代码了(顺便说一下,我认为TextBlock默认继承;没有默认样式可以覆盖)。


+1 'TargetType="{x:Type ContentControl}' 也适用于 TextBlock。 - biju
1
这并不能解决继承问题。例如,如果您想在“窗口”级别上指定默认颜色,并为窗口内的特定区域指定另一种颜色。在这种情况下,您将不得不在该级别重新定义所有样式。 - Pavlo Glazkov
1
抱歉,但这也不能与TextBlock一起使用(如果这是一个要求的话 - “数百个元素”意味着可能是这样)。TextBlock继承自FrameworkElement,而Label继承自ContentControl继承自Control继承自FrameworkElement。 - kiwipom
@ Pavlo Glazov:这个问题揭示了两个问题:1:Label和CheckBox不会从它们的基类继承其前景属性,因此在针对这样一个祖先的样式中设置前景是无意义的。2:如果您可以指定控件可以从其在可视树中的父级复制一些属性,那将是很好的。您在自己的答案中提供了一个可能的解决方案,但我认为它并不是非常令人满意。 - Dabblernl
@Dabblernl - Foreground 属性会被逻辑树中的每个元素继承。只有当该属性在元素本地或样式中设置值时,继承才会停止。在 LabelCheckBox 控件中,默认样式继承也会停止。我覆盖了默认样式使其从父级控件中继续继承。如果你不相信,请试一试-它是有效的。 - Pavlo Glazkov
显示剩余2条评论

0

在WPF中确实不容易。但你可以尝试这样做

<StackPanel>
        <StackPanel.Resources>
            <Style x:Key="CommonStyle">
                <Setter Property="TextElement.Foreground" Value="Red" />
            </Style>
        </StackPanel.Resources>
        <Label Content="Test" Style="{StaticResource CommonStyle}" />
        <Label Content="Test" Style="{StaticResource CommonStyle}"/>
        <CheckBox Content="Checkbox" Style="{StaticResource CommonStyle}"/>

    </StackPanel>

3
没什么用,因为你需要在所有地方都设置这个样式。 - MikeKulls

0

如果你需要配置数百个单独的元素,那肯定会很烦人,但我假设你不会有数百种不同的元素类型。

既然如此,你可以为你的类型创建样式,并在那里设置前景色。

理想情况下,这可能在一个ResourceDictionary中,每个样式都引用一个公共的前景色,比如

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <SolidColorBrush x:Key="appForegroundColor" Color="Red" />

    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="{StaticResource appForegroundColor}" />
    </Style>

    <!-- Create styles for Element Types here -->

</ResourceDictionary>

然后,您可以将此资源字典应用于需要它的窗口,例如:

<Window ...>
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid>
        <TextBlock Text="Foo" />
    </Grid>
</Window>

这对于位于模板中的DataTemplate之类的TextBlocks无效。 - Amadeusz Wieczorek

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