Is there any way to combine mutliple styles in XAML to make a new style that has all of the desired settings?
For example (pseudo code);
<Style x:key="A"> ... </Style> <Style x:key="B"> ... </Style> <Style x:key="Combined"> <IncludeStyle Name="A"/> <IncludeStyle Name="B"/> ... other properties. </Style>
I know that there is a BasedOn
property for styles, but that feature will only take you so far. I am really just looking for an easy way (in XAML) to create these 'combined' styles. But like I said before, I doubt it exists, unless anyone has heard of such a thing??
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With