XAML如何在超越BasedOn的情况下合并样式?

54

有没有一种方法可以将多个XAML样式合并成一个新的样式,其中包含所有所需的设置?

例如(伪代码);

<Style x:key="A">
 ...
</Style>

<Style x:key="B">
 ...
</Style>

<Style x:key="Combined">
 <IncludeStyle Name="A"/>
 <IncludeStyle Name="B"/>
 ... other properties.
</Style>

我知道样式有一个BasedOn属性,但是这个功能不能满足我的需求。我真的只是在寻找一种简单的方式(在XAML中)来创建这些“组合”样式。但就像我之前说过的,我怀疑这种东西是否存在,除非有人听说过这样的事情?


这个回答解决了你的问题吗?如何在WPF中应用多个样式 - StayOnTarget
2个回答

66

您可以创建自定义的标记扩展,将样式属性和触发器合并为一个单独的样式。您只需要向您的命名空间添加一个MarkupExtension-派生类,并定义MarkupExtensionReturnType属性,然后就可以开始运行了。

这是一个扩展,它将允许您使用“类似于css”的语法合并样式。

MultiStyleExtension.cs

[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
    private string[] resourceKeys;

    /// <summary>
    /// Public constructor.
    /// </summary>
    /// <param name="inputResourceKeys">The constructor input should be a string consisting of one or more style names separated by spaces.</param>
    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.");
    }

    /// <summary>
    /// Returns a style that merges all styles with the keys specified in the constructor.
    /// </summary>
    /// <param name="serviceProvider">The service provider for this markup extension.</param>
    /// <returns>A style that merges all styles with the keys specified in the constructor.</returns>
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        Style resultStyle = new Style();
        foreach (string currentResourceKey in resourceKeys)
        {
            object key = currentResourceKey;
            if (currentResourceKey == ".")
            {
                IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                key = service.TargetObject.GetType();
            }
            Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;
            if (currentStyle == null)
                throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
            resultStyle.Merge(currentStyle);
        }
        return resultStyle;
    }
}

public static class MultiStyleMethods
{
    /// <summary>
    /// Merges the two styles passed as parameters. The first style will be modified to include any 
    /// information present in the second. If there are collisions, the second style takes priority.
    /// </summary>
    /// <param name="style1">First style to merge, which will be modified to include information from the second one.</param>
    /// <param name="style2">Second style to merge.</param>
    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];
    }
}

你的示例可以通过以下方式解决:

<Style x:key="Combined" BasedOn="{local:MultiStyle A B}">
      ... other properties.
</Style>

我们通过合并两种其他样式“A”和“B”,在内置的BasedOn属性(用于样式继承)中定义了一个名为“Combined”的新样式。按照惯例,我们可以选择向新的“Combined”样式添加其他属性。

其他示例:

这里,我们定义了4个按钮样式,并可以以各种组合方式使用它们,减少了重复:

<Window.Resources>
    <Style TargetType="Button" x:Key="ButtonStyle">
        <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="RedButtonStyle">
        <Setter Property="Foreground" Value="Red" />
    </Style>
    <Style TargetType="Button" x:Key="BoldButtonStyle">
        <Setter Property="FontWeight" Value="Bold" />
    </Style>
</Window.Resources>

<Button Style="{local:MultiStyle ButtonStyle GreenButtonStyle}" Content="Green Button" />
<Button Style="{local:MultiStyle ButtonStyle RedButtonStyle}" Content="Red Button" />
<Button Style="{local:MultiStyle ButtonStyle GreenButtonStyle BoldButtonStyle}" Content="green, bold button" />
<Button Style="{local:MultiStyle ButtonStyle RedButtonStyle BoldButtonStyle}" Content="red, bold button" />

你甚至可以使用 "." 语法将一些附加样式与某种类型(上下文相关)的“当前”默认样式合并:

<Button Style="{local:MultiStyle . GreenButtonStyle BoldButtonStyle}"/>
上面的代码将默认样式与两个辅助样式合并,其中 TargetType="{x:Type Button}" 是指按钮类型。 鸣谢:我在bea.stollnitz.com上找到了MultiStyleExtension的原始想法,并对其进行了修改,以支持“.”符号用于引用当前样式。

4
我已成功地使用这个方法来合并两种样式,但是我遇到了一个小问题。VS 2010 WPF Designer 对这种方法有问题。我可以按照这里的详细说明来合并样式并使用 MultiStyle,在没有问题的情况下构建/运行代码。但是,WPF设计师在DataTemplate中使用此方法时会发出警告。有人遇到/解决了这个问题吗? - Joe K
2
@JoeK 我曾经遇到过完全相同的问题,并在这里发布了一个相关问题:https://dev59.com/D17Va4cB1Zd3GeqPOOu1。到目前为止,我唯一的解决方案是在设计模式下禁用该扩展,这并不是最理想的解决方法。 - Alain
4
在使用资源字典中的基于点符号的样式定义时,存在一个使用“BasedOn”时出现的错误。以下是修复方法:IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); if (service.TargetObject is Style) { key = ((Style)service.TargetObject).TargetType; } else { key = service.TargetObject.GetType(); } - MoonStom
3
我已经修改了你的代码,现在它可以接受最多10个Style对象作为参数,而不是使用样式键进行重构,同时合并发生在构造函数中:https://dotnetfiddle.net/r464VS - Epsil0neR
1
如果有人感兴趣,我已将Epsiloner.Wpf.Extensions.MultiStyleExtension添加到我的WPF相关库中:https://github.com/Epsil0neR/Epsiloner.Wpf.Core或https://www.nuget.org/packages/Epsiloner.Wpf.Core/ - Epsil0neR
显示剩余5条评论

3
您可以在样式中使用"BasedOn"属性,例如:
<Style x:Key="BaseButtons" TargetType="{x:Type Button}">
        <Setter Property="BorderThickness" Value="0"></Setter>
        <Setter Property="Background" Value="Transparent"></Setter>
        <Setter Property="Cursor" Value="Hand"></Setter>
        <Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
<Style x:Key="ManageButtons" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButtons}">
        <Setter Property="Height" Value="50"></Setter>
        <Setter Property="Width" Value="50"></Setter>
</Style>
<Style x:Key="ManageStartButton" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButtons}">
        <Setter Property="FontSize" Value="16"></Setter>
</Style>

并使用:

<Button Style="{StaticResource ManageButtons}"></Button>
<Button Style="{StaticResource ManageStartButton}"></Button>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接