在WPF中,如何将多个样式应用于FrameworkElement
?例如,我有一个已经有风格的控件。我也有一个单独的风格,我想添加它,而不会吹掉第一个。样式具有不同的TargetTypes,因此我不能只用另一个扩展一个。
答案 0 :(得分:144)
我认为简单的答案是你不能做(至少在这个版本的WPF中)你想做什么。
也就是说,对于任何特定元素,只能应用一个Style。
但是,如上所述,也许您可以使用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>
希望这会有所帮助。
注意:强>
特别需要注意的一件事。如果您将第二种样式中的TargetType
(在上面的第一组xaml中)更改为ButtonBase
,则不会应用这两种样式。但是,请查看下面的xaml以解决该限制。基本上,这意味着您需要为Style指定一个键并使用该键引用它。
<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>
答案 1 :(得分:42)
答案 2 :(得分:31)
但你可以从另一个扩展..看一下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>
答案 3 :(得分:17)
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>
答案 4 :(得分:3)
这可以通过创建一个帮助类来使用和包装样式。提到的CompoundStyle here显示了如何做到这一点。有多种方法,但最简单的方法是执行以下操作:
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
希望有所帮助。
答案 5 :(得分:2)
使用AttachedProperty
设置多个样式,如下面的代码:
public class Css
{
public static string GetClass(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(ClassProperty);
}
public static void SetClass(DependencyObject element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(ClassProperty, value);
}
public static readonly DependencyProperty ClassProperty =
DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css),
new PropertyMetadata(null, OnClassChanged));
private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ui = d as FrameworkElement;
Style newStyle = new Style();
if (e.NewValue != null)
{
var names = e.NewValue as string;
var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in arr)
{
Style style = ui.FindResource(name) as Style;
foreach (var setter in style.Setters)
{
newStyle.Setters.Add(setter);
}
foreach (var trigger in style.Triggers)
{
newStyle.Triggers.Add(trigger);
}
}
}
ui.Style = newStyle;
}
}
Usege:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:style_a_class_like_css"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="325">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Red" >
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="TextBlock" x:Key="Green" >
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style TargetType="TextBlock" x:Key="Size18" >
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="6"/>
</Style>
<Style TargetType="TextBlock" x:Key="Bold" >
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<StackPanel>
<Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
<Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
<Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>
</StackPanel>
</Window>
结果:
答案 6 :(得分:1)
如果您没有触及任何特定属性,则可以获得目标类型为FrameworkElement的样式的所有基本属性和公共属性。然后,您可以为所需的每种目标类型创建特定的风格,而无需再次复制所有这些常用属性。
答案 7 :(得分:1)
如果通过使用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>
答案 8 :(得分:1)
有时你可以通过嵌套面板来解决这个问题。假设您有一个Style更改Foreground而另一个更改FontSize,您可以将后一个应用于TextBlock,并将其放在Grid中,其Style是第一个。在某些情况下,这可能是有帮助的,也可能是最简单的方法,但它不会解决所有问题。
答案 9 :(得分:1)
当您覆盖SelectStyle时,您可以通过如下反射获取GroupBy属性:
public override Style SelectStyle(object item, DependencyObject container)
{
PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
{
return this.TitleStyle;
}
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
{
return this.DateStyle;
}
return null;
}
答案 10 :(得分:0)
如果您尝试将唯一样式应用于单个元素作为基本样式的补充,则可以采用完全不同的方式来做到这一点,IMHO更好地实现了可读性和可维护性代码。
这是非常常见的需要调整每个单独元件的参数。定义仅用于一个元素的字典样式非常麻烦,难以维护或理解。为了避免只为一次性因素调整创造风格,看了我的回答我自己的问题在这里在这里: