根据Windows主题调整WPF普通控件的样式

3

我已经阅读了很多关于WPF主题、皮肤、样式等方面的内容,但仍有一些我无法实现的东西。

我有自定义控件,根据操作系统主题进行样式设置,每个主题文件中都有不同的样式(Aero.NormalColor.xaml、Luna.NormalColor.xaml或Aero2.NormalColor.xaml),这个功能非常好用。

我没有在App.xaml中加载/强制任何主题,每个控件(如按钮)保持其样式取决于操作系统主题。所以我在XP上看到XP按钮,在Windows 7上看到Win7按钮,在Windows 8上看到Win8按钮。

我还有ResourceDictionaries,它们在App.xaml中被加载,包含了针对不同普通wpf控件的“命名”(显式x:Key)样式。它们看起来像这样:

<Style x:Key="BlackComboBox" TargetType="{x:Type ComboBox}"></Style>

我这样使用它们

<ComboBox Style="{StaticResource BlackComboBox}"></ComboBox>

目前,我的BlackComboBox在所有Windows系统(XP/7/8)上都是相同的。

我想要实现的是,在不必为每个需要特定操作系统主题的控件创建子类的情况下,使这些普通控件具有不同的样式,因此BlackComboBox可以在每个操作系统上都不同。

我已经尝试在一个主题文件中使用相同的键来设置样式,但似乎并没有起作用。

我考虑在运行时加载不同的ResourceDictionary,其中包含所需操作系统版本的样式:

  • 但这看起来很丑陋。
  • 我不喜欢必须检查System.Environment.OSVersion。
  • 而且它将不是主题相关的,而是操作系统相关的。

对我来说,最好的方法似乎是能够在主题文件中拥有“命名”样式,以某种方式覆盖ResourceDictionaries中的样式。

感谢您的帮助!


你是在为每个主题创建不同的模板,还是你的命名样式只是设置简单的属性(如背景、边框笔刷等)? - John Bowen
对于其中一些人来说,这只是使用画笔的风格,但在大多数情况下,我还会更改模板(在风格内部)。 - StrAbZ
我知道Actipro的库支持XP、Vista、7、Office等本地控件的主题,但它们创建了SharedResourceKeys,在XAML样式中引用如下:"{DynamicResource {x:Static themes:AssetResourceKeys.MenuItemBorderNormalBrushKey}}",并且它们有一种ResourceManager来控制它。 - Alan
谢谢。但我正在寻找不依赖第三方库的东西。我只是找不到一种方法,而不必为我需要样式的每个控件创建子类。 - StrAbZ
1个回答

1
我认为唯一的方法是为每个主题创建资源字典,就像您创建自定义控件并希望为每个主题提供不同外观时所做的那样。然后,您将为每个 ComboBox 创建一个 Style,并使用 ResourceKey 派生类(例如 ComponentResourceKey)作为 Style 的 x:Key,在每个主题的资源字典中使用相同的 x:Key 值。然后,当您引用该 Style 时,您将使用 DynamicResource 引用该 ResourceKey。
因此,一个简化的示例是创建一个新的 WpfApplication(例如,我将其命名为 WpfResourceKeys)。在这种情况下,我将主题资源字典放在主程序集中,因此我进入 AssemblyInfo.cs 并将 ThemeInfo 的第一个参数(即 themeDictionaryLocation)设置为 SourceAssembly。
然后创建一个名为“themes”的文件夹,并在其中为要支持的每个主题创建一个资源字典。例如,aero.normalcolor.xaml、aero2.normalcolor.xaml、luna.normalcolor.xaml、classic.xaml 等。
在每个ResourceDictionary中定义一个ComboBox或其他控件的样式,并将其x:Key设置为相同的ResourceKey。最简单的方法是使用ComponentResourceKey。在我的情况下,我将使用TextBox,因为我只需要设置背景色,而不管每个主题定义的模板如何,都会被尊重。例如:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:local="clr-namespace:WpfResourceKeys"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="TextBox" 
        x:Key="{ComponentResourceKey 
            ResourceId=Foo, 
            TypeInTargetAssembly={x:Type local:MainWindow}}">
        <Setter Property="Background" Value="Purple" />
    </Style>
</ResourceDictionary>

在我的情况下,我只是将这个放到每个主题的xaml文件中,但是为了测试,Background设置器的值不同。所以在我的aero2.normalcolor.xaml中,setter值为紫色,在classic.xaml中,setter值为橙色。当我在Windows 8上运行我的测试时,如果我切换到高对比度主题之一,TextBox将变为橙色。

然后,在您要引用它的位置,您将使用DynamicResource而不是StaticResource,因为您不会在窗口或app.xaml的资源中定义样式(因为您希望框架考虑操作系统主题来定位它)。
<Window x:Class="WpfResourceKeys.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfResourceKeys"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox Style="{DynamicResource ResourceKey={ComponentResourceKey 
            ResourceId=Foo, 
            TypeInTargetAssembly={x:Type local:MainWindow}}}" Text="ABC" />
    </Grid>

您只需要确保使用与主题字典中定义的等效资源键相同。对于ComponentResourceKey来说,这意味着ResourceId和TypeInTargetAssembly是等效的。


它像魔法一样运行,非常感谢!我希望(并认为)它将涵盖我大部分(或许全部)的需求! - StrAbZ

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