.net 如何在 WPF 中应用多种样式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16096/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 09:38:53  来源:igfitidea点击:

How to apply multiple styles in WPF

.netwpfstyles

提问by MojoFilter

In WPF, how would I apply multiple styles to a FrameworkElement? For instance, I have a control which already has a style. I also have a separate style which I would like to add to it without blowing away the first one. The styles have different TargetTypes, so I can't just extend one with the other.

在 WPF 中,如何将多种样式应用于FrameworkElement? 例如,我有一个已经有样式的控件。我也有一个单独的风格,我想在不影响第一个风格的情况下添加它。样式有不同的 TargetType,所以我不能只用另一种来扩展。

回答by cplotts

I think the simple answer is that you can't do (at least in this version of WPF) what you are trying to do.

我认为简单的答案是你不能做(至少在这个版本的 WPF 中)你想做的事。

That is, for any particular element only one Style can be applied.

也就是说,对于任何特定元素,只能应用一种样式。

However, as others have stated above, maybe you can use BasedOnto help you out. Check out the following piece of loose xaml. In it you will see that I have a base style that is setting a property that exists on the base class of the element that I want to apply two styles to. And, in the second style which is based on the base style, I set another property.

但是,正如上面其他人所说,也许您可​​以使用它BasedOn来帮助您。查看以下松散的 xaml。在其中,您将看到我有一个基本样式,它设置了一个属性,该属性存在于我想要应用两种样式的元素的基类中。并且,在基于基本样式的第二种样式中,我设置了另一个属性。

So, the idea here ... is if you can somehow separate the properties that you want to set ... according the inheritance hierarchy of the element you want to set multiple styles on ... you might have a workaround.

所以,这里的想法......是如果你能以某种方式分离你想要设置的属性......根据你想要设置多种样式的元素的继承层次结构......你可能有一个解决方法。

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


Hope this helps.


希望这可以帮助。

Note:

笔记:

One thing in particular to note. If you change the TargetTypein the second style (in first set of xaml above) to ButtonBase, the two Styles do not get applied. However, check out the following xaml below to get around that restriction. Basically, it means you need to give the Style a key and reference it with that key.

特别要注意的一件事。如果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>

回答by Wilka

Bea Stollnitz had a good blog postabout using a markup extension for this, under the heading "How can I set multiple styles in WPF?"

Bea Stollnitz 有一篇关于为此使用标记扩展的很好的博客文章,标题为“如何在 WPF 中设置多种样式?”

That blog is dead now, so I'm reproducing the post here

那个博客现在已经死了,所以我在这里复制了这篇文章



WPF and Silverlight both offer the ability to derive a Style from another Style through the “BasedOn” property. This feature enables developers to organize their styles using a hierarchy similar to class inheritance. Consider the following styles:

WPF 和 Silverlight 都提供了通过“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>

With this syntax, a Button that uses RedButtonStyle will have its Foreground property set to Red and its Margin property set to 10.

使用此语法,使用 RedButtonStyle 的 Button 会将其 Foreground 属性设置为 Red,并将其 Margin 属性设置为 10。

This feature has been around in WPF for a long time, and it's new in Silverlight 3.

此功能在 WPF 中已经存在很长时间了,它是 Silverlight 3 中的新功能。

What if you want to set more than one style on an element? Neither WPF nor Silverlight provide a solution for this problem out of the box. Fortunately there are ways to implement this behavior in WPF, which I will discuss in this blog post.

如果你想在一个元素上设置多个样式怎么办?WPF 和 Silverlight 都没有为这个问题提供开箱即用的解决方案。幸运的是,有一些方法可以在 WPF 中实现这种行为,我将在这篇博文中讨论。

WPF and Silverlight use markup extensions to provide properties with values that require some logic to obtain. Markup extensions are easily recognizable by the presence of curly brackets surrounding them in XAML. For example, the {Binding} markup extension contains logic to fetch a value from a data source and update it when changes occur; the {StaticResource} markup extension contains logic to grab a value from a resource dictionary based on a key. Fortunately for us, WPF allows users to write their own custom markup extensions. This feature is not yet present in Silverlight, so the solution in this blog is only applicable to WPF.

WPF 和 Silverlight 使用标记扩展为属性提供需要某些逻辑才能获取的值。标记扩展很容易通过 XAML 中围绕它们的大括号的存在来识别。例如,{Binding} 标记扩展包含从数据源获取值并在发生更改时更新它的逻辑;{StaticResource} 标记扩展包含根据键从资源字典中获取值的逻辑。幸运的是,WPF 允许用户编写自己的自定义标记扩展。此功能在 Silverlight 中尚不存在,因此本博客中的解决方案仅适用于 WPF。

Othershave written great solutions to merge two styles using markup extensions. However, I wanted a solution that provided the ability to merge an unlimited number of styles, which is a little bit trickier.

其他人已经编写了很好的解决方案来使用标记扩展合并两种样式。但是,我想要一个能够合并无限数量样式的解决方案,这有点棘手。

Writing a markup extension is straightforward. The first step is to create a class that derives from MarkupExtension, and use the MarkupExtensionReturnType attribute to indicate that you intend the value returned from your markup extension to be of type Style.

编写标记扩展很简单。第一步是创建一个派生自 MarkupExtension 的类,并使用 MarkupExtensionReturnType 属性来指示您希望从标记扩展返回的值属于 Style 类型。

[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}

Specifying inputs to the markup extension

指定标记扩展的输入

We'd like to give users of our markup extension a simple way to specify the styles to be merged. There are essentially two ways in which the user can specify inputs to a markup extension. The user can set properties or pass parameters to the constructor. Since in this scenario the user needs the ability to specify an unlimited number of styles, my first approach was to create a constructor that takes any number of strings using the “params” keyword:

我们想为我们的标记扩展的用户提供一种简单的方法来指定要合并的样式。用户可以通过两种方式指定标记扩展的输入。用户可以设置属性或将参数传递给构造函数。由于在这种情况下用户需要能够指定无限数量的样式,我的第一种方法是使用“params”关键字创建一个接受任意数量字符串的构造函数:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

My goal was to be able to write the inputs as follows:

我的目标是能够按如下方式编写输入:

<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />

Notice the comma separating the different style keys. Unfortunately, custom markup extensions don't support an unlimited number of constructor parameters, so this approach results in a compile error. If I knew in advance how many styles I wanted to merge, I could have used the same XAML syntax with a constructor taking the desired number of strings:

请注意分隔不同样式键的逗号。不幸的是,自定义标记扩展不支持无限数量的构造函数参数,因此这种方法会导致编译错误。如果我事先知道我想要合并多少个样式,我可以使用相同的 XAML 语法和一个带有所需数量字符串的构造函数:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

As a workaround, I decided to have the constructor parameter take a single string that specifies the style names separated by spaces. The syntax isn't too bad:

作为一种解决方法,我决定让构造函数参数采用单个字符串,指定由空格分隔的样式名称。语法还不错:

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.");
    }
}

Calculating the output of the markup extension

计算标记扩展的输出

To calculate the output of a markup extension, we need to override a method from MarkupExtension called “ProvideValue”. The value returned from this method will be set in the target of the markup extension.

要计算标记扩展的输出,我们需要覆盖 MarkupExtension 中名为“ProvideValue”的方法。此方法返回的值将设置在标记扩展的目标中。

I started by creating an extension method for Style that knows how to merge two styles. The code for this method is quite simple:

我首先为 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];
    }
}

With the logic above, the first style is modified to include all information from the second. If there are conflicts (e.g. both styles have a setter for the same property), the second style wins. Notice that aside from copying styles and triggers, I also took into account the TargetType and BasedOn values as well as any resources the second style may have. For the TargetType of the merged style, I used whichever type is more derived. If the second style has a BasedOn style, I merge its hierarchy of styles recursively. If it has resources, I copy them over to the first style. If those resources are referred to using {StaticResource}, they're statically resolved before this merge code executes, and therefore it isn't necessary to move them. I added this code in case we're using DynamicResources.

使用上述逻辑,第一个样式被修改为包含第二个样式的所有信息。如果存在冲突(例如,两种样式都有相同属性的 setter),则第二种样式获胜。请注意,除了复制样式和触发器之外,我还考虑了 TargetType 和 BasedOn 值以及第二种样式可能拥有的任何资源。对于合并样式的TargetType,我使用了更派生的类型。如果第二个样式有一个 BasedOn 样式,我会递归地合并它的样式层次结构。如果它有资源,我将它们复制到第一种样式。如果使用 {StaticResource} 引用这些资源,则在执行此合并代码之前静态解析它们,因此无需移动它们。我添加了此代码以防我们使用 DynamicResources。

The extension method shown above enables the following syntax:

上面显示的扩展方法启用以下语法:

style1.Merge(style2);

This syntax is useful provided that I have instances of both styles within ProvideValue. Well, I don't. All I get from the constructor is a list of string keys for those styles. If there was support for params in the constructor parameters, I could have used the following syntax to get the actual style instances:

如果我在 ProvideValue 中有两种样式的实例,则此语法很有用。好吧,我不知道。我从构造函数中得到的只是这些样式的字符串键列表。如果在构造函数参数中支持 params,我可以使用以下语法来获取实际的样式实例:

<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}

But that doesn't work. And even if the params limitation didn't exist, we would probably hit another limitation of markup extensions, where we would have to use property-element syntax instead of attribute syntax to specify the static resources, which is verbose and cumbersome (I explain this bug better in a previous blog post). And even if both those limitations didn't exist, I would still rather write the list of styles using just their names – it is shorter and simpler to read than a StaticResource for each one.

但这不起作用。即使不存在 params 限制,我们也可能会遇到标记扩展的另一个限制,我们必须使用属性元素语法而不是属性语法来指定静态资源,这既冗长又麻烦(我解释一下在之前的博文中发现错误更好)。即使这两个限制都不存在,我仍然宁愿只使用它们的名称来编写样式列表——它比每个样式的 StaticResource 更短、更容易阅读。

The solution is to create a StaticResourceExtension using code. Given a style key of type string and a service provider, I can use StaticResourceExtension to retrieve the actual style instance. Here is the syntax:

解决方案是使用代码创建一个 StaticResourceExtension。给定一个字符串类型的样式键和一个服务提供者,我可以使用 StaticResourceExtension 来检索实际的样式实例。这是语法:

Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

Now we have all the pieces needed to write the ProvideValue method:

现在我们有了编写 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;
}

Here is a complete example of the usage of the MultiStyle markup extension:

以下是 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" />

enter image description here

在此处输入图片说明

回答by Arcturus

But you can extend from another.. take a look at the BasedOn property

但是你可以从另一个扩展..看一下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>

回答by Arcturus

WPF/XAML doesn't provide this functionality natively, but it does provide the extensibility to allow you to do what you want.

WPF/XAML 本身不提供此功能,但它确实提供了可扩展性以允许您执行所需的操作。

We ran into the same need, and ended up creating our own XAML Markup Extension (which we called "MergedStylesExtension") to allow us to create a new Style from two other styles (which, if needed, could probably be used multiple times in a row to inherit from even more styles).

我们遇到了同样的需求,最终创建了我们自己的 XAML 标记扩展(我们称之为“MergedStylesExtension”),以允许我们从其他两种样式(如果需要,可以在一个样式中多次使用)创建一个新样式行以从更多样式继承)。

Due to a WPF/XAML bug, we need to use property element syntax to use it, but other than that it seems to work ok. E.g.,

由于 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>

I recently wrote about it here: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

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

回答by Shahar Prish

This is possible by creating a helper class to use and wrap your styles. CompoundStyle mentioned hereshows how to do it. There are multiple ways, but the easiest is to do the following:

这可以通过创建一个辅助类来使用和包装您的样式来实现。这里提到的 CompoundStyle展示了如何做到这一点。有多种方法,但最简单的是执行以下操作:

<TextBlock Text="Test"
    local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>

Hope that helps.

希望有帮助。

回答by google dev

Use AttachedPropertyto set multiple styles like following code:

使用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>

Result:

结果:

enter image description here

在此处输入图片说明

回答by hillin

Sometimes you can approach this by nesting panels. Say you have a Style which changes Foreground and another changes FontSize, you can apply the latter one on a TextBlock, and put it in a Grid which its Style is the first one. This might help and might be the easiest way in some cases, though it won't solve all the problems.

有时您可以通过嵌套面板来解决这个问题。假设您有一个更改 Foreground 的 Style 和另一个更改 FontSize 的样式,您可以将后一个应用于 TextBlock,并将其放入其样式为第一个的 Grid 中。这可能会有所帮助,并且在某些情况下可能是最简单的方法,尽管它不能解决所有问题。

回答by Sérgio Henrique

When you override SelectStyle you can get GroupBy property via reflection like below:

当您覆盖 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;
    }

回答by Greg

if you are not touching any specific properties, you can get all base and common properties to the style which's target type would be FrameworkElement. then, you can create specific flavours for each target types you need, without need of copying all those common properties again.

如果您不涉及任何特定属性,则可以获取目标类型为 FrameworkElement 的样式的所有基本和通用属性。然后,您可以为您需要的每个目标类型创建特定的风格,而无需再次复制所有这些公共属性。

回答by Dave

You can probably get something similar if applying this to a collection of items by the use of a StyleSelector, i have used this to approach a similar problem in using different styles on TreeViewItems depending on the bound object type in the tree. You may have to modify the class below slightly to adjust to your particular approach but hopefully this will get you started

如果通过使用 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;
        }
    }
}

You then apply this as so

然后你这样应用

 <TreeView>
     <TreeView.ItemContainerStyleSelector
         <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
                                         NewStyle="{StaticResource NewItemStyle}" />
     </TreeView.ItemContainerStyleSelector>
  </TreeView>