如何在WPF中应用多种样式
在WPF中,如何将多种样式应用于FrameworkElement
?例如,我有一个已经具有样式的控件。我还有另外一种风格,我想在不破坏第一个风格的情况下增加它。样式具有不同的TargetType,因此我不能仅将其中一种进行扩展。
解决方案
回答
但是我们可以从另一个扩展..看一下BasedOn属性
<Style TargetType="TextBlock"> <Setter Property="Margin" Value="3" /> </Style> <Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}"> <Setter Property="VerticalAlignment" Value="Top" /> </Style>
回答
如果我们不使用任何特定的属性,则可以将样式的所有基本属性和通用属性都获得,该样式的目标类型将是FrameworkElement。然后,我们可以为所需的每种目标类型创建特定的口味,而无需再次复制所有这些通用属性。
回答
我认为简单的答案是(至少在此版本的WPF中)我们无法执行尝试执行的操作。
也就是说,对于任何特定元素,只能应用一种样式。
但是,正如上面其他人所述,也许我们可以使用BasedOn
来。检查以下一块松散的xaml。在其中,我们将看到我有一个基本样式,该样式正在设置一个属性,该属性存在于要应用两种样式的元素的基类上。并且,在基于基本样式的第二种样式中,我设置了另一个属性。
因此,这里的想法是...如果我们可以某种方式将要设置的属性分开...根据要在其上设置多种样式的元素的继承层次结构,则可能会有解决方法。
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <Style x:Key="baseStyle" TargetType="FrameworkElement"> <Setter Property="HorizontalAlignment" Value="Left"/> </Style> <Style TargetType="Button" BasedOn="{StaticResource baseStyle}"> <Setter Property="Content" Value="Hello World"/> </Style> </Page.Resources> <Grid> <Button Width="200" Height="50"/> </Grid> </Page>
希望这可以帮助。
笔记:
特别要注意的一件事。如果将第二种样式(上述第一组xaml中的)的TargetType更改为ButtonBase,则不会应用这两种样式。但是,请查看下面的xaml以解决该限制。基本上,这意味着我们需要为"样式"提供一个键,并使用该键对其进行引用。
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <Style x:Key="baseStyle" TargetType="FrameworkElement"> <Setter Property="HorizontalAlignment" Value="Left"/> </Style> <Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}"> <Setter Property="Content" Value="Hello World"/> </Style> </Page.Resources> <Grid> <Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/> </Grid> </Page>
回答
如果通过使用StyleSelector将其应用于项目集合,则可能会得到类似的结果,我已经使用它来解决类似的问题,具体取决于树中绑定的对象类型,对TreeViewItems使用不同的样式。我们可能需要稍微修改下面的类以适应特定方法,但希望这可以入门
public class MyTreeStyleSelector : StyleSelector { public Style DefaultStyle { get; set; } public Style NewStyle { get; set; } public override Style SelectStyle(object item, DependencyObject container) { ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container); //apply to only the first element in the container (new node) if (item == ctrl.Items[0]) { return NewStyle; } else { //otherwise use the default style return DefaultStyle; } } }
然后按原样应用
<TreeView> <TreeView.ItemContainerStyleSelector <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}" NewStyle="{StaticResource NewItemStyle}" /> </TreeView.ItemContainerStyleSelector> </TreeView>
回答
WPF / XAML本身不提供此功能,但确实提供了可扩展性以允许我们执行所需的操作。
我们遇到了同样的需求,最终创建了自己的XAML标记扩展(我们称为" MergedStylesExtension"),以允许我们从其他两种样式中创建新样式(如果需要,可以在一个样式中多次使用)行以继承更多样式)。
由于WPF / XAML错误,我们需要使用属性元素语法来使用它,但除此之外看来还可以。例如。,
<Button Content="This is an example of a button using two merged styles"> <Button.Style> <ext:MergedStyles BasedOn="{StaticResource FirstStyle}" MergeStyle="{StaticResource SecondStyle}"/> </Button.Style> </Button>
我最近在这里写到:
http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
回答
Bea Stollnitz在"如何在WPF中设置多种样式?"标题下有一篇不错的博客文章,内容涉及为此使用标记扩展。
那个博客现在已经死了,所以我在这里复制帖子
WPF和Silverlight都提供了通过BasedOn属性从另一个Style派生一个Style的功能。此功能使开发人员可以使用类似于类继承的层次结构来组织样式。考虑以下样式:
<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。
其他人则写了很棒的解决方案,以使用标记扩展来合并两种样式。但是,我想要一个能够合并无限数量的样式的解决方案,这有点棘手。
编写标记扩展很简单。第一步是创建一个从MarkupExtension派生的类,并使用MarkupExtensionReturnType属性来指示我们希望标记扩展名返回的值为Style类型。
[MarkupExtensionReturnType(typeof(Style))] public class MultiStyleExtension : MarkupExtension { }
指定标记扩展的输入
Wed希望为我们的标记扩展程序的用户提供一种简单的方法来指定要合并的样式。用户可以通过两种方式指定标记扩展的输入。用户可以设置属性或者将参数传递给构造函数。由于在这种情况下,用户需要能够指定数量不受限制的样式,因此我的第一种方法是使用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的方法。从此方法返回的值将在标记扩展的目标中设置。
我从创建样式的扩展方法开始,该方法知道如何合并两种样式。此方法的代码非常简单:
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);
如果我在ProvideValue中同时具有这两种样式的实例,则此语法很有用。好吧,我没有。我从构造函数中得到的只是这些样式的字符串键列表。如果构造函数参数中支持参数,则可以使用以下语法获取实际的样式实例:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles) { }
但这不起作用。即使没有params限制,我们也可能会遇到标记扩展的另一个限制,在这种情况下,我们将不得不使用property-element语法而不是attribute语法来指定静态资源,这既冗长又繁琐(我会更好地解释此错误在之前的博客文章中)。即使这两个限制都不存在,我还是宁愿只使用样式名称来编写样式列表,它比每个样式的StaticResource更短,更容易阅读。
解决方案是使用代码创建一个StaticResourceExtension。给定字符串类型的样式键和服务提供者,我可以使用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标记扩展用法的完整示例:
<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" />