【问题标题】:Style to choose suitable foreground according to background color风格根据背景颜色选择合适的前景
【发布时间】:2011-09-05 09:26:02
【问题描述】:

我有一个 WPF 应用程序,它附带一组由项目类型定义的标签、文本框等默认样式(未定义显式键)。

在应用程序中使用了两种主要容器,一种具有深色背景,另一种具有浅色背景,因此有时使用黑色作为标签的前景色是正确的,有时则严重错误。另一方面,编辑器的样式总是比较传统的浅色背景和深色前景,所以我不能只将所有子元素的前景设置为相反。

有没有一种优雅的方法可以让我的标签(也许还有 TextBlocks)根据“他们的”背景来决定他们的前景色?我只想在两种颜色之间切换,因此不需要自动对比度最大化,只需要一些阈值来避免白底上的白色字体。

我也不想定义两组默认​​样式,我强烈寻找某种方法使我的单个 Label-Default-Style 适合两种背景变体。

是否有可能(并且在没有太多性能损失的情况下可行)向样式添加触发器/绑定以评估当前背景颜色?

另外,我对如何为某些 FrameworkElements(尤其是容器/面板)干净地设置背景颜色而不遇到上述问题的最佳实践感兴趣。

这是我尝试过的(当然是简化的):

  <UniformGrid>
  <UniformGrid.Resources>
  <!-- SimpleStyles: Label -->
     <Style x:Key="{x:Type Label}" TargetType="{x:Type Label}">
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="VerticalContentAlignment" Value="Top"/>
        <Setter Property="Template">
           <Setter.Value>
              <ControlTemplate TargetType="{x:Type Label}">
                 <Border>
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                 </Border>
                 <ControlTemplate.Triggers>
                    <Trigger Property="Background" Value="Black">
                       <Setter Property="Foreground" Value="Red"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                       <Setter Property="Foreground" Value="#888888"/>
                    </Trigger>
                 </ControlTemplate.Triggers>
              </ControlTemplate>
           </Setter.Value>
        </Setter>
     </Style>
  </UniformGrid.Resources>
  <Label x:Name="bgSetExplicitly" Background="Black">abc
  </Label>
  <Border Background="Black">
     <Label x:Name="bgInheritedFromParent" >abc
     </Label>
  </Border>
  <Label>abc
  </Label>
  <Label>abc
  </Label>

如果标签具有显式背景集 (x:Name=bgSetExplicitly),但如果背景是从 VisualTree 中的父级“继承”的 (x:Name=" bgInheritedFromParent"),它不是。 我希望该样式可以评估“有效背景”(无论它来自何处)并为此背景选择合适的前景画笔。

【问题讨论】:

  • 我终于接受了罗伯特的建议:每个设置自定义背景的容器也必须设置一个合适的前景。所有控件(标签,文本框等)都必须尊重这些设置,s.th。无论将 TextBox 放在哪个容器中,它都会看起来不错。最初,我试图找到一个解决方案,即使应用程序中的某些样式不遵循该规则也可以工作,主要是为了稳健性,因为我们无法在每次更改基本样式后对所有对话框进行运行时测试。但事实证明这比我最初想象的要困难,所以我暂时放弃了。

标签: wpf styles background-color foreground


【解决方案1】:

最简单的做法是将您的 TextBox 等的前景色绑定到其包含元素的背景,然后使用值转换器进行更改。如果您只使用两种颜色,这尤其容易。

举个例子……

  public class ColorSwapper : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
System.Globalization.CultureInfo culture)
    {
      if (value == null) { return DependencyProperty.UnsetValue; }

      Color srcColor = (value as SolidColorBrush).Color;
      if (srcColor == Colors.Black)
      {
        return new SolidColorBrush(Colors.White);
      }
      else
      {
        return new SolidColorBrush(Colors.Black);
      }
    }
...

还有一些示例 XAML....

<Grid Grid.Column="1" Name="RightGrid" Background="White">
  <TextBlock Text="Hello Nurse!" Foreground="{Binding ElementName=RightGrid, Path=Background, Converter={StaticResource ColorSwapper}}"/>
</Grid>

当然,您可能希望根据您的特定需求进行一些调整和清理,但这会做到。

【讨论】:

  • 这肯定行得通,但这会让我将这个额外的绑定添加到我的应用程序中的每个 TextBlock 或标签,这将是数百个。由于在您的示例中您绑定到一个命名元素,因此每个 TextBlock 的 Binding 必须是不同的,具体取决于其周围环境,因此它不能在全局样式中工作。
  • 然后让转换器沿着可视化树向上走并确定要从那里转换的背景颜色。无论如何,我确实提到您必须根据您的特定需求对其进行调整......
  • 我以为值转换器中的“值”不在可视化树中
  • 可以,具体取决于您绑定的内容。您还可以使用“RelativeSource”绑定来定位具有您正在寻找的值的所需祖先。有很多方法可以给这只猫剥皮。
  • 我会尝试获得“下一个可用的背景颜色”,也许我会以我的中心样式执行此操作,而无需触及所有这些单个控件。
【解决方案2】:

这个问题似乎暗示了一个更深层次的问题。我猜你还没有整合前景色的管理,所以你有一个应用程序,它的代码或样式可以在所有地方设置前景色。现在您正在处理其中的一个含义。

我会面对更深层次的问题并解决它。我真的不明白你为什么反对创建默认样式的想法,但假设你有充分的理由不这样做(除了“我应该在某个时候创建​​默认样式,但现在我没有,而且我的应用程序变得很大,这太难了,”在这种情况下,我有一个不同的建议),创建全球资源怎么样?

在应用程序的资源字典中为控件的前景色和背景色创建对象,并修复 XAML,使其不直接引用画笔,而是使用 DynamicResource 标记扩展从资源字典中获取它们。在代码中,不要直接将控件上的Foreground 属性设置为画笔,而是使用GetResource 来获取画笔。并以同样的方式设置背景。

完成此操作后,如果您想在应用程序中全局更改前景色/背景色,只需更改资源即可。

这基本上是您开始使 WPF 应用程序可换肤的方式,这似乎是您要走的路。

【讨论】:

  • 我知道它已经关门了,但你能看看我的问题并发表评论吗?我使用了动态资源,但遇到了问题。 stackoverflow.com/questions/5667987/…
  • 我不反对默认样式,我已经使用它们了。只是我希望每种控件类型只有一种默认样式,只要有可能。这正是因为我想在某个中心主题中定义所有样式,而不是分散在整个应用程序中。
  • 即:我的单一默认样式并不适合所有情况,因为这里的背景颜色可以是浅色的,也可以是深色的,我不想为整个应用程序中的各个控件设置样式。所以我需要找到一种方法让默认样式足够智能以适应它的环境。
  • 设置一个前景色和背景色怎么不能同时设置?
  • 是的,我认为这是我正在寻找的“最佳实践”。背景和前景应始终成对设置。对于我的应用程序(不是发布的示例),我们将不得不重新设计一些样式,以确保它们都遵守此准则并正确使用前景和背景。会有点痛,但我觉得这是正确的方法。
【解决方案3】:

这是另一种可能的方法。如果您的应用程序只有所有深色/浅色背景及其赞美,您可以尝试这样的方法,使用我在其他答案中提到的“ColorSwapper”。

<Window>
  <Window.Resources>
    <local:ColorSwapper x:Key="swapper"/>

    <SolidColorBrush x:Key="BackBrush" Color="Black"/>
    <!--<SolidColorBrush x:Key="BackBrush" Color="White"/>-->

    <Style TargetType="{x:Type TextBlock}">
      <Setter Property="Foreground" Value="{Binding Source={StaticResource BackBrush}, Converter={StaticResource swapper}}"/>
    </Style>

  </Window.Resources>

  <Grid Background="{StaticResource BackBrush}">
    <TextBlock FontSize="24" FontWeight="Bold">ABC</TextBlock>
  </Grid>
</Window>

现在,每当您更改“BackBrush”的颜色值时,所有相关的前景色都会自动更新。根据您的讨论,我认为这也将满足您的风格要求。无论如何,您可能必须制作小型模组以确保这种方法适合您的确切场景。

【讨论】:

  • 是的,这更接近,将额外定义转移到窗口(或其他容器)。但我正在寻找一个解决方案,其中 TextBlock-Style 单独完成所有操作,对窗口或容器没有进一步要求。考虑一个包含许多组框的窗口,其中组框具有交替的背景颜色,我想对所有这些组框使用相同的一组默认样式。如果组框不是组框而是具有不同背景的简单边框,我也想使用相同的样式。抱歉,完全解释我的动机很长。
  • 我想将所有功能放入标签/文本块等默认样式的进一步原因:我有其他元素不会根据背景改变颜色,例如TextBox 有自己的纯黑色边框,带有白色背景和黑色前景,适用于深色和浅色背景。
  • 好的,我给你两个合理的解决方案。基本上,您想要制作一种能够读懂您的想法并且不会发生的风格。对于您正在谈论的复杂功能类型,您将不得不结合技术,并且可能会做一些代码隐藏。同时,如果您需要更多帮助来解决您的问题,您将不得不聘请我担任顾问。祝你好运。
  • 是的,没错。感谢您所做的所有努力,如果我最终获得了我的读心神风格 - 或者如果我决定选择其他方式,我将更新这篇文章。不幸的是,我现在没有顾问预算。
猜你喜欢
  • 2011-02-07
  • 2013-09-03
  • 1970-01-01
  • 2019-08-30
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多