【问题标题】:Setting Foreground Color of Entire Window设置整个窗口的前景色
【发布时间】:2011-03-16 15:46:39
【问题描述】:

我想为我的所有元素设置前景色(文本) 你会认为这很容易,但事实并非如此......

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

这没有任何效果......我可以让它工作的唯一方法是如果我专门为每个元素设置 Foreground 属性。如果您有数百个元素等,这会很烦人。

也许你知道一种方法?

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    这是因为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 inself 中)定义此类控件的默认样式(如 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 - 不,这将永远有效。它唯一做的就是恢复被样式设置器破坏的元素默认样式的继承。例如,如果您有这样的树:Window -> Grid -> ListBox -> ListBoxItem -> StackPanel -> Label。如果您在Window 上设置Foreground 属性,则该值将由Label 继承,如下所示:Window (Red) -> Grid (Red [inherited]) -> ListBox (Red [inherited]) - > ListBoxItem (Red [inherited]) -> StackPanel (Red [inherited]) -> Label (Red [Binding to StackPanel.(TextElement.Foreground)])。
    • 虽然这通常有效,但我遇到了 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')
    • (我通过将模板设置为默认样式的框架解决了这个问题,并且在模板中不包含边框。)
    【解决方案2】:

    遗憾的是,样式在 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>
    

    至少你不必到处复制相同的设置器代码(顺便说一句,我认为 TextBlock 默认继承;没有覆盖的默认样式)。

    【讨论】:

    • +1 'TargetType="{x:Type ContentControl}' 也适用于文本块
    • 这不会解决继承问题。例如,如果您想在Window 级别上指定默认颜色,并为窗口内的特定区域指定另一种颜色。在这种情况下,您必须在该级别再次定义所有样式。
    • 对不起,但这也不适用于 TextBlock(如果这是一个要求 - '数百个元素'意味着它可能是)。 TextBlock继承FrameworkElement,而Label继承ContentControl继承Control继承FrameworkElement。
    • @Pavlo Glazov :这个问题揭示了两个问题:1:标签和复选框从它们的基类继承它们的 Foreground 属性,因此将 Foreground 设置为针对的样式这样的祖先毫无意义。 2:如果您可以指定控件可以从可视树中的父级复制它们的一些属性,那就太好了。您在自己的答案中给出了一个可能的解决方案,但恕我直言,这不是很令人满意。
    • @IanR:你是对的,这不适用于 TextBlock。 FrameworkElement 没有 Foreground 太可惜了。
    【解决方案3】:

    是的,在 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>
    

    【讨论】:

    • 这并没有真正实现太多,因为您需要在任何地方设置样式
    【解决方案4】:

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

    既然如此,您可以做的一件事是为您的类型创建样式,并在那里设置前景色。

    理想情况下,这可能在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>
    

    【讨论】:

    • 这不适用于 TextBlocks 内的模板,如 DataTemplate
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 2015-10-23
    • 1970-01-01
    • 2011-03-06
    • 2021-10-02
    • 1970-01-01
    相关资源
    最近更新 更多