如何在WPF中应用多个样式

时间:2008-08-19 12:47:45

标签: .net wpf styles

在WPF中,如何将多个样式应用于FrameworkElement?例如,我有一个已经有风格的控件。我也有一个单独的风格,我想添加它,而不会吹掉第一个。样式具有不同的TargetTypes,因此我不能只用另一个扩展一个。

11 个答案:

答案 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>

我最近在这里写到: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

答案 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>

结果:

enter image description here

答案 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更好地实现了可读性和可维护性代码。

这是非常常见的需要调整每个单独元件的参数。定义仅用于一个元素的字典样式非常麻烦,难以维护或理解。为了避免只为一次性因素调整创造风格,看了我的回答我自己的问题在这里在这里:

https://stackoverflow.com/a/54497665/1402498