WPF 和 Silverlight 都提供了从
另一个 Style 通过“BasedOn”属性。此功能使
开发人员使用类似于类的层次结构来组织他们的样式
遗产。考虑以下样式:
<Style TargetType="Button" x:Key="BaseButtonStyle">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red" />
</Style>
使用此语法,使用 RedButtonStyle 的 Button 将具有
Foreground 属性设置为 Red,Margin 属性设置为 10。
这个功能在 WPF 中已经存在很长时间了,它是新的
Silverlight 3.
如果你想在一个元素上设置多个样式怎么办? WPF都不是
Silverlight 也没有为这个问题提供开箱即用的解决方案。
幸运的是,有一些方法可以在 WPF 中实现这种行为,我
将在这篇博文中讨论。
WPF 和 Silverlight 使用标记扩展来提供属性
需要一些逻辑才能获得的值。标记扩展很容易
可以通过在它们周围的大括号来识别
XAML。例如,{Binding} 标记扩展包含逻辑
从数据源中获取值并在发生更改时更新它;这
{StaticResource} 标记扩展包含从中获取值的逻辑
基于键的资源字典。幸运的是,WPF 允许
用户编写自己的自定义标记扩展。此功能不
还存在于 Silverlight 中,因此本博客中的解决方案仅
适用于 WPF。
Others
编写了使用标记合并两种样式的出色解决方案
扩展名。但是,我想要一个能够提供以下功能的解决方案
合并无限数量的样式,这有点棘手。
编写标记扩展很简单。第一步是
创建一个派生自 MarkupExtension 的类,并使用
MarkupExtensionReturnType 属性表明您打算
从您的标记扩展返回的值为 Style 类型。
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
指定标记扩展的输入
我们希望为使用我们的标记扩展程序的用户提供一种简单的方法
指定要合并的样式。基本上有两种方式
用户可以指定标记扩展的输入。用户可以
设置属性或将参数传递给构造函数。由于在这
场景用户需要能够指定无限数量的
样式,我的第一种方法是创建一个构造函数,它接受任何
使用“params”关键字的字符串数:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
我的目标是能够编写如下输入:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
请注意分隔不同样式键的逗号。很遗憾,
自定义标记扩展不支持无限数量的
构造函数参数,所以这种方法会导致编译错误。
如果我提前知道我想要合并多少种风格,我可以拥有
使用相同的 XAML 语法,构造函数采用所需的数字
字符串:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
作为一种解决方法,我决定让构造函数参数采用
单个字符串,指定由空格分隔的样式名称。这
语法还不错:
private string[] resourceKeys;
public MultiStyleExtension(string inputResourceKeys)
{
if (inputResourceKeys == null)
{
throw new ArgumentNullException("inputResourceKeys");
}
this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (this.resourceKeys.Length == 0)
{
throw new ArgumentException("No input resource keys specified.");
}
}
计算标记扩展的输出
要计算标记扩展的输出,我们需要覆盖
来自 MarkupExtension 的方法称为“ProvideValue”。返回的值
from 这个方法将被设置在标记扩展的目标中。
我首先为 Style 创建了一个扩展方法,它知道如何
合并两种风格。这个方法的代码很简单:
public static void Merge(this Style style1, Style style2)
{
if (style1 == null)
{
throw new ArgumentNullException("style1");
}
if (style2 == null)
{
throw new ArgumentNullException("style2");
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
// This code is only needed when using DynamicResources.
foreach (object key in style2.Resources.Keys)
{
style1.Resources[key] = style2.Resources[key];
}
}
通过上面的逻辑,第一个样式被修改为包含所有
来自第二个的信息。如果存在冲突(例如两种样式
具有相同属性的设置器),第二种样式获胜。注意
除了复制样式和触发器之外,我还考虑了
TargetType 和 BasedOn 值以及第二个的任何资源
风格可能有。对于合并样式的 TargetType,我使用了
无论哪种类型更派生。如果第二种样式有一个 BasedOn
样式,我递归地合并其样式层次结构。如果有
资源,我将它们复制到第一种样式。如果这些资源
提到使用 {StaticResource},它们之前是静态解析的
此合并代码执行,因此无需移动
他们。我添加了这段代码以防我们使用 DynamicResources。
上面显示的扩展方法启用了以下语法:
style1.Merge(style2);
如果我有两种样式的实例,此语法很有用
在提供值内。好吧,我没有。我从构造函数中得到的只是
这些样式的字符串键列表。如果有支持
params 在构造函数参数中,我可以使用以下
获取实际样式实例的语法:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
但这不起作用。即使不存在参数限制,
我们可能会遇到标记扩展的另一个限制,其中
我们将不得不使用属性元素语法而不是属性
指定静态资源的语法,它是冗长且
麻烦(我在previous blog
post 中更好地解释了这个错误)。
即使这两个限制都不存在,我仍然宁愿
仅使用名称编写样式列表 - 它更短且
比一个静态资源更容易阅读。
解决方案是使用代码创建一个 StaticResourceExtension。给定
字符串类型的样式键和服务提供者,我可以使用
StaticResourceExtension 来检索实际的样式实例。这是
语法:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider)
作为样式;
现在我们有了编写 ProvideValue 方法所需的所有部分:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider)
作为样式;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
这里是 MultiStyle 标记用法的完整示例
扩展:
<Window.Resources>
<Style TargetType="Button" x:Key="SmallButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="Button" x:Key="GreenButtonStyle">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style TargetType="Button" x:Key="BoldButtonStyle">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Window.Resources>
<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />