样式根据背景颜色选择合适的前景

发布于 2024-11-10 08:30:05 字数 2335 浏览 0 评论 0原文

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

在应用程序中,使用了两种主要容器,一种具有深色背景,一种具有浅色背景,因此有时使用黑色作为标签的前景色是正确的,有时却是完全错误的。另一方面,编辑器的样式始终采用浅色背景和深色前景的传统风格,因此我不能将所有子元素的前景设置为相反。

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

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

是否可以(并且可行,不会对性能造成太大影响)向样式添加触发器/绑定,以评估当前背景颜色?

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

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

  <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"),则标签的背景选择得很好, 它不是。 我希望这种风格可以评估“有效背景”(无论它来自哪里)并为此背景选择合适的前景画笔。

I have a WPF application which ships with a set of default styles for Label, TextBox etc. defined by item types (no explicit keys defined).

Within the application there are two main containers used, one with dark background and one with light background, such that sometimes it's right to use black as the foreground color for a Label and sometimes its dramatically wrong. On the other hand, editors are always styled rather traditionally with light background and dark foreground, so I cannot just set the foreground for all child elements to the inverse.

Is there an elegant way to make my labels (and maybe TextBlocks as well) to decide about their foreground color dependent on 'their' background? I only want to switch between two colors, thus no auto-contrast-maximization needed, only some threshold to avoid white font on white ground.

I also don't want to define two sets of default styles, I strongly search for some way to make my single Label-Default-Style be appropriate for both background variants.

Is it possible (and feasible without too much performance hit) to add a trigger/binding to the style, which evaluates the current background color?

Alternatively, I would be interested in best practices how to cleanly set background-colors for certain FrameworkElements, especially containers/panels, without running into the problems described above.

Here is what I tried (simplified of course):

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

You can see that the label's background is chosen nicely, if the Label has an explicit background set (x:Name=bgSetExplicitly), but if the background is 'inherited' from the parent in the VisualTree (x:Name="bgInheritedFromParent"), it's not.
I would love to have that working that the style can evaluate the "effective background" (no matter where it comes from) and choose an appropriate foreeground-brush for this background.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

聽兲甴掵 2024-11-17 08:30:05

这个问题似乎隐含着更深层次的问题。我猜想您还没有整合前景色的管理,因此您的应用程序具有在各处设置前景色的代码或样式。现在你正在处理其中的影响之一。

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

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

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

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

This question seems to imply a deeper issue. I would guess that you haven't consolidated the management of foreground colors, and so you have an application that has code or styles that set foreground colors all over the place. And now you're dealing with one of the implications of that.

I'd face the deeper issue and fix that. I don't really understand why you're resistant to the idea of creating default styles, but assuming for the moment that you have a good reason not to do this (other than "I should have created default styles at some point, but now that I haven't and my application has gotten big, it's just too hard," in which case, I have a different suggestion), how about creating global resources?

Create objects in the application's resource dictionary for the controls' foreground and background colors, and fix the XAML so that instead of referencing brushes directly, it uses the DynamicResource markup extension to get them from the resource dictionary. In code, instead of setting the Foreground property on the controls to a brush directly, use GetResource to get the brush. And set the background the same way.

Once you've done this, if you want to change foreground/background colors globally in your application, you just change the resources.

This is basically how you start making a WPF application skinnable, which seems to be the road you're going down.

剑心龙吟 2024-11-17 08:30:05

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

作为一个例子......

  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>

当然,您可能想要根据您的特定需求进行一些调整和清理,但这就可以了。

The easiest thing to do is bind the foreground color of your TextBoxes, etc. to the background of their containing element, and use a value converter to make the change. This is especially easy if you are only ever using two colors.

As an example....

  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);
      }
    }
...

And some example XAML....

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

Of course you will probably want to make some tweaks and cleanup according to your specific needs, but that will do it.

錯遇了你 2024-11-17 08:30:05

这是另一种可能的方法。如果您的应用程序仅具有所有深色/浅色背景及其赞美,您可以尝试这样的方法,使用我在其他答案中提到的“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”的颜色值时,所有相关的前景色都会自动更新。根据您的讨论,我认为这也能满足您的风格要求。无论如何,您可能必须制作小型模组才能确保这种方法适合您的具体场景。

Here is another possible approach. If your application only has ALL dark/light backgrounds and their compliments, you can try an approach like this, using the 'ColorSwapper' that I mentioned in my other answer.

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

Now whenever you change the color value of 'BackBrush', all of the related foreground colors will automatically update. Based on your discussions, I think that this will also meet your style requirements. At any rate, you will likely have to make small mods to ensure that this approach will fit your exact scenario.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文