在WPF中,我如何将多个样式应用于FrameworkElement
?例如,我有一个已经有风格的控件.我也有一个单独的风格,我想添加它,而不会吹掉第一个.样式有不同的TargetTypes,所以我不能只用另一个扩展一个.
我认为简单的答案是你不能做(至少在这个版本的WPF中)你想要做什么.
也就是说,对于任何特定元素,只能应用一个Style.
但是,正如其他人在上面所述,也许你可以BasedOn
用来帮助你.看看以下松散的xaml.在其中你会看到我有一个基本样式,它设置一个属性,该属性存在于我想要应用两个样式的元素的基类上.并且,在基于基本样式的第二种样式中,我设置了另一个属性.
所以,这里的想法是...如果你能以某种方式分离你想要设置的属性...根据你想要设置多个样式的元素的继承层次结构...你可能有一个解决方法.
希望这可以帮助.
注意:
特别需要注意的一件事.如果您TargetType
将第二种样式(在上面的第一组xaml中)更改为ButtonBase
,则两个样式不会被应用.但是,请查看下面的xaml以解决该限制.基本上,这意味着您需要为Style指定一个键并使用该键引用它.
Bea Stollnitz在"我如何在WPF中设置多个样式?"标题下有一篇关于使用标记扩展的博文.
那个博客现在已经死了,所以我在这里复制这篇文章
WPF和Silverlight都提供了通过"BasedOn"属性从另一个Style派生样式的功能.此功能使开发人员能够使用类继承的层次结构来组织其样式.考虑以下样式:
使用此语法,使用RedButtonStyle的Button将其Foreground属性设置为Red,其Margin属性设置为10.
这个功能已经在WPF中存在很长时间了,它是Silverlight 3中的新功能.
如果要在元素上设置多个样式,该怎么办?WPF和Silverlight都没有为开箱即用的问题提供解决方案.幸运的是,有很多方法可以在WPF中实现这种行为,我将在这篇博文中讨论.
WPF和Silverlight使用标记扩展来提供具有需要某些逻辑来获取的值的属性.通过在XAML中围绕它们的大括号的存在,可以轻松识别标记扩展.例如,{Binding}标记扩展包含从数据源获取值并在发生更改时更新它的逻辑; {StaticResource}标记扩展包含基于密钥从资源字典中获取值的逻辑.对我们来说幸运的是,WPF允许用户编写自己的自定义标记扩展.Silverlight中尚未提供此功能,因此本博客中的解决方案仅适用于WPF.
其他人已经编写了很好的解决方案来使用标记扩展来合并两种样 但是,我想要一个能够合并无限数量样式的解决方案,这有点棘手.
编写标记扩展很简单.第一步是创建一个派生自MarkupExtension的类,并使用MarkupExtensionReturnType属性指示您希望从标记扩展返回的值为Style类型.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
我们希望为标记扩展的用户提供一种指定要合并的样式的简单方法.用户可以通过两种方式指定标记扩展的输入.用户可以设置属性或将参数传递给构造函数.由于在这种情况下用户需要能够指定无限数量的样式,我的第一种方法是创建一个构造函数,使用"params"关键字获取任意数量的字符串:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
我的目标是能够按如下方式编写输入:
注意分隔不同样式键的逗号.遗憾的是,自定义标记扩展不支持无限数量的构造函数参数,因此这种方法会导致编译错误.如果我事先知道我想合并多少样式,我可以使用相同的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"的方法.从此方法返回的值将在标记扩展的目标中设置.
我开始为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];
}
}
使用上面的逻辑,第一个样式被修改为包含第二个样式的所有信息.如果存在冲突(例如,两种样式都具有相同属性的setter),则第二种样式获胜.请注意,除了复制样式和触发器之外,我还考虑了TargetType和BasedOn值以及第二种样式可能具有的任何资源.对于合并样式的TargetType,我使用了更多派生的类型.如果第二种样式具有BasedOn样式,我会递归地合并其样式层次结构.如果它有资源,我将它们复制到第一个样式.如果使用{StaticResource}引用这些资源,则在执行此合并代码之前它们将被静态解析,因此无需移动它们.我添加了这段代码,以防我们使用DynamicResources.
上面显示的扩展方法启用以下语法:
style1.Merge(style2);
如果我在ProvideValue中有两个样式的实例,则此语法很有用.好吧,我没有.我从构造函数中获得的是这些样式的字符串键列表.如果构造函数参数中支持params,我可以使用以下语法来获取实际的样式实例:
public MultiStyleExtension(params Style[] styles)
{
}
但这不起作用.即使params限制不存在,我们可能会遇到标记扩展的另一个限制,我们必须使用属性 - 元素语法而不是属性语法来指定静态资源,这是冗长和繁琐的(我解释这个在以前的博客文章中更好的bug ).即使这两个限制都不存在,我仍然宁愿只使用它们的名称来编写样式列表 - 它比每个的StaticResource更简单,更简单.
解决方案是使用代码创建StaticResourceExtension.给定string类型和服务提供者的样式键,我可以使用StaticResourceExtension来检索实际样式实例.这是语法:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
现在我们已经拥有编写ProvideValue方法所需的所有部分:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
以下是MultiStyle标记扩展的使用的完整示例:
但是你可以从另一个扩展..看一下BasedOn属性
WPF/XAML本身不提供此功能,但它确实提供了可扩展性,使您可以执行所需的操作.
我们遇到了同样的需求,并最终创建了我们自己的XAML标记扩展(我们称之为"MergedStylesExtension"),以允许我们从其他两种样式创建一个新样式(如果需要,可以在一个样式中多次使用)从更多样式继承的行).
由于WPF/XAML错误,我们需要使用属性元素语法来使用它,但除此之外它似乎工作正常.例如,
我最近在这里写了一篇文章:http: //swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/