XAML Combine styles going beyond BasedOn?

You can make a custom markup extensions that will merge styles properties and triggers into a single style. All you need to do is add a MarkupExtension-derived class to your namespace with the MarkupExtensionReturnType attribute defined and you're off and running.

Here is an extension that will allow you to merge styles using a "css-like" syntax.

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];
    }
}

Your example would then be solved by going:

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

We have defined a new style named "Combined" by merging two other styles "A" and "B" within the built-in BasedOn attribute (used for style inheritance). We can optionally add other properties to the new "Combined" style as per usual.

Other Examples:

Here, we define 4 button styles, and can use them in various combinations with little repetition:

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

You can even use the "." syntax to merge the "current" default style for a type (context-dependent) with some additional styles:

<Button Style="{local:MultiStyle . GreenButtonStyle BoldButtonStyle}"/>

The above will merge the default style for TargetType="{x:Type Button}" with the two supplemental styles.

Credit

I found the original idea for the MultiStyleExtension at bea.stollnitz.com and modified it to support the "." notation to reference the current style.


You can use BasedOn property in style, for example:

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

and use:

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