Applying Style to child elements in Silverlight 4 - silverlight

In Silverlight 4 (using Expression Blend 4), how can I alter the Font size of a TextBox within the style of the Border containing it? I'm converting the style from WPF to Silverlight (fun always). Here is what I have:
<Style x:Key="Title" TargetType="Border">
<Setter Property="TextBlock.VerticalAlignment" Value="Center"/>
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
<Setter Property="TextBlock.FontSize" Value="48"/>
<Setter Property="TextBlock.Foreground" Value="{StaticResource TextForeground}"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Background" Value="{StaticResource TitleBackground}"/>
<Setter Property="Padding" Value="25,0"/>
</Style>
It is not working. It gives me the following exception in the designer:
Edits:
Ok, I know this is possible in WPF. Is it simply not possible in Silverlight (without taking on a whole theme construct as Xin suggests?)

Actually you might be able to get what you want from silverlight toolkit themes. You can find it here (Theming -> Theme Browser).
Update:
First you need to create a class that inherits from the Theme (System.Windows.Controls.Theming). I basically copied from the source code and renamed it.
/// <summary>
/// Implicitly applies the border theme to all of its descendent
/// FrameworkElements.
/// </summary>
/// <QualityBand>Preview</QualityBand>
public class BorderTheme : Theme
{
/// <summary>
/// Stores a reference to a Uri referring to the theme resource for the class.
/// </summary>
private static Uri ThemeResourceUri = new Uri("/theming;component/Theme.xaml", UriKind.Relative);
/// <summary>
/// Initializes a new instance of the ExpressionDarkTheme class.
/// </summary>
public BorderTheme()
: base(ThemeResourceUri)
{
var a = ThemeResourceUri;
}
/// <summary>
/// Gets a value indicating whether this theme is the application theme.
/// </summary>
/// <param name="app">Application instance.</param>
/// <returns>True if this theme is the application theme.</returns>
public static bool GetIsApplicationTheme(Application app)
{
return GetApplicationThemeUri(app) == ThemeResourceUri;
}
/// <summary>
/// Sets a value indicating whether this theme is the application theme.
/// </summary>
/// <param name="app">Application instance.</param>
/// <param name="value">True if this theme should be the application theme.</param>
public static void SetIsApplicationTheme(Application app, bool value)
{
SetApplicationThemeUri(app, ThemeResourceUri);
}
}
Then you just need to create a resource dictionary and name it Theme.xaml, and put all your styles inside.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Border">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Background" Value="{StaticResource TitleBackground}"/>
<Setter Property="Padding" Value="25,0"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="FontSize" Value="48"/>
<Setter Property="Foreground" Value="{StaticResource TextForeground}"/>
</Style>
</ResourceDictionary>
Last, wrap your Border with it!
<local:BorderTheme>
<Border>
<TextBlock Text="TextBlock"/>
</Border>
</local:BorderTheme>
That's it. You should be able to see the styles applied to your Border as well as your TextBlock. :)

Related

Type key for style not found

I created a custom user control called FlatButton deriving from Button:
XAML:
<Button
x:Class="Program.GUI.Controls.FlatButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:GUIControls="clr-namespace:Program.GUI.Controls"
mc:Ignorable="d">
<Button.Resources>
<ResourceDictionary>
<Style x:Key="{x:Type GUIControls:FlatButton}" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource ResourceKey={x:Type Button}}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GUIControls:FlatButton}">
<Grid x:Name="LayoutRoot" Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}">
<TextBlock
x:Name="Text"
Text="{TemplateBinding Content}"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverBackground}"/>
<Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverForeground}"/>
</Trigger>
<Trigger Property="IsMouseCaptured" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickBackground}"/>
<Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickForeground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Button.Resources>
</Button>
CS:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Program.GUI.Controls {
/// <summary>
/// Interaction logic for FlatButton.xaml
/// </summary>
public partial class FlatButton : Button {
public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register(
"HoverBackground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty HoverForegroundProperty = DependencyProperty.Register(
"HoverForeground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty ClickBackgroundProperty = DependencyProperty.Register(
"ClickBackground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty ClickForegroundProperty = DependencyProperty.Register(
"ClickForeground",
typeof(Brush),
typeof(FlatButton)
);
public Brush HoverBackground { get; set; }
public Brush HoverForeground { get; set; }
public Brush ClickBackground { get; set; }
public Brush ClickForeground { get; set; }
static FlatButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatButton), new FrameworkPropertyMetadata(typeof(FlatButton)));
}
public FlatButton() {
this.InitializeComponent();
}
}
}
The Problem is when I try to create an extending style the base style is not found:
MainWindow XAML:
<Style x:Key="DefaultDarkButtonThemeStyle" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource {x:Type GUIControls:FlatButton}}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource ColorTextPrimaryDark}"/>
<Setter Property="HoverBackground" Value="{DynamicResource ColorPrimaryDarkLight}"/>
<Setter Property="HoverForeground" Value="{DynamicResource ColorTextPrimaryDarkLight}"/>
<Setter Property="ClickBackground" Value="{DynamicResource ColorPrimaryDarkLightLight}"/>
<Setter Property="ClickForeground" Value="{DynamicResource ColorTextPrimaryDarkLightLight}"/>
</Style>
Exception:
System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '45' and line position '47'.'
Inner Exception
Exception: Cannot find resource named 'Program.GUI.Controls.FlatButton'. Resource names are case sensitive.
If you develop a custom control like FlatButton, you should create a separate style that either resides in a theme resource dictionary (e.g. Themes/Generic.xaml) if you develop a custom control library or any other dedicated resource dictionary. Regardless of which path you choose, you can merge the corresponding resource dictionary into the application resources or any other resource dictionary on a lower scope in order to make the style accessible within that scope (apply it automatically if it is implicit or creating styles based on it, whatever).
Your partial XAML definition is the usual approach for creating UserControls, but not for custom controls. The style for FlatButton in the resources of the Button markup can only be resolved within the scope of this local resource dictionary, which means the FlatButton itself or down its visual tree. Look at the static resource lookup behavior to understand how the style is being resovled and why it fails.
The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.
The lookup process then traverses the logical tree upward to the parent element and its resource dictionary. This process continues until the root element is reached.
App resources are checked. App resources are those resources within the resource dictionary that is defined by the Application object for your WPF app.
Consequently, when your DefaultDarkButtonThemeStyle is created in your main window and tries to resolve the GUIControls:FlatButton base style, it does not find it, because it is not defined within the window resources or anywhere up the visual tree or the application resources.
However, if you simply created a theme or regular resource dictionary that contained the style and included it in the window or application resource, it would be resolved successfully.
For more information on creating custom controls, including how to set up styles and resource dictionaries, you can refer to Control authoring overview.

Why MultiDataTrigger Binding failed in my CustomControl?

Here is code in Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2.Controls">
<Style TargetType="{x:Type local:SampleControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SampleControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True" />
<Condition Property="{Binding RelativeSource={RelativeSource Self}, Path=MyProperty}" Value="1" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="#cde8ff"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Here is code in SampleControl.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp2.Controls
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfApp2.Controls"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfApp2.Controls;assembly=WpfApp2.Controls"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Browse to and select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:SampleControl/>
///
/// </summary>
public class SampleControl : Control
{
static SampleControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SampleControl), new FrameworkPropertyMetadata(typeof(SampleControl)));
}
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(SampleControl), null);
}
}
Here are all the files:
After the program ran, it reports an error:
System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'Property' property of type 'Condition'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'
No matter I delete IsMouseOver or MyProperty condition binding, it still reports this.
What's wrong with my codes?
Condition.Property is not a DependencyProperty and cannot be a Binding.Target. You have to use the Condition.Binding property to set up a data binding:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=MyProperty}" Value="1" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="#cde8ff"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
But since you are triggering on the templated object itself, you can use a simple MultiTrigger:
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="MyProperty" Value="1" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background" Value="#cde8ff"/>
</MultiTrigger.Setters>
</MultiTrigger>

How to change the style of a row in a WPF DataGrid control when a property of the item in that row changes

I've got a DataGrid control in my WPF application that contains an object. There is a boolean property of that object which can be changed by user actions. I need the style of the row to change when that property's value changes.
I have written a class that descends from StyleSelector:
public class LiveModeSelector : StyleSelector {
public Style LiveModeStyle { get; set; }
public Style NormalStyle { get; set; }
public override Style SelectStyle( object item, DependencyObject container ) {
DataGridRow gridRow = container as DataGridRow;
LPRCamera camera = item as LPRCamera;
if ( camera != null && camera.IsInLiveMode ) {
return LiveModeStyle;
}
return NormalStyle;
}
}
The View Model class in question implements INotifyPropertyChanged, and it raises the PropertyChanged event when the property in question changes.
// Note: The ModuleMonitor class implements INotifyPropertyChanged and raises the PropertyChanged
// event in the SetAndNotify generic method.
public class LPRCamera : ModuleMonitor, ICloneable {
. . .
public bool IsInLiveMode {
get { return iIsInLiveMode; }
private set { SetAndNotify( "IsInLiveMode", ref iIsInLiveMode, value ); }
}
private bool iIsInLiveMode;
. . .
/// </summary>
public void StartLiveMode() {
IsInLiveMode = true;
. . .
}
public void StopLiveMode() {
IsInLiveMode = false;
. . .
}
}
The value of the property is changed when the user performs the required action, but the style doesn't change.
I have placed a breakpoint in the SelectStyle method and I see the breakpoint gets hit when the control is first loaded, but it does not get hit when the property's value changes.
What am I missing?
I found a way to do this that grew out of #Rachel's answer to my question. However, the code details differ somewhat and I want to show exactly what works.
The first step was to combine the two different Styles into one for the DataGridRow class:
<Style TargetType="DataGridRow" x:Key="CameraStyle">
<Setter Property="Foreground" Value="{DynamicResource TextForeground}" />
<Setter Property="Background" Value="{DynamicResource DataBackground}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsInLiveMode}" Value="True">
<Setter Property="Foreground" Value="Red" />
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
The second step was to set the DataGrid control's RowStyle property to this new style:
<DataGrid . . .
RowStyle={StaticResource CameraStyle}">
. . .
</DataGrid>
This works. The foreground and background of the row changes when the user puts the LPRCamera associated with that row into Live Mode and changes back to normal when they take it out of Live Mode, which is what I was going for.
Thanks #Rachel!
I don't think a StyleSelector listens for PropertyChange notifications, so it doesn't get re-run when the IsInLiveMode property gets changed.
Put your style in a DataTrigger based on IsInLiveMode instead, and it will get re-evaluated anytime the property change notification gets raised.
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}" x:Key="Style1">
<Setter Property="Background" Value="Red" />
</Style>
<Style TargetType="{x:Type DataGridRow}" x:Key="Style2">
<Setter Property="Background" Value="Blue" />
</Style>
</DataGrid.Resources>
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="RowStyle" Value="{StaticResource Style1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyDataGrid, Path=DataContext.IsInLiveMode}" Value="True">
<Setter Property="RowStyle" Value="{StaticResource Style2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>

How to create a brush that supports both dark and light themes?

I'm working with WP7, and i'd like to create a custom brush (as a local resource) that uses different colors for dark and light themes (for instace, red if the theme is black and blue if it's white).
How do i do it?
Thanks!
The integrated/system brushes do not change their properties based on the theme, a different set of brushes is included based on the current theme. You can see the various versions of this in %programfiles%\Microsoft SDKs\Windows Phone\v7.1\Design
I wrote a custom ResourceDictionary that implements theme support in the exact same way: by loading the appropriate theme dictionary depending on the light/dark theme.
Here is a sample that uses it. It works in the Visual Studio designer as well as Blend, but doesn't support white theme preview in Blend because Blend loads resources in a way that cannot be reproduced.
<Application.Resources>
<custom:ThemeResourceDictionary>
<custom:ThemeResourceDictionary.LightResources>
<ResourceDictionary Source="/ThemeManagement;component/Resources/Light.xaml" />
</custom:ThemeResourceDictionary.LightResources>
<custom:ThemeResourceDictionary.DarkResources>
<ResourceDictionary Source="/ThemeManagement;component/Resources/Dark.xaml" />
</custom:ThemeResourceDictionary.DarkResources>
</custom:ThemeResourceDictionary>
</Application.Resources>
The above code loads in the resources from two different files, but they could just as easily be declared inline like any other ResourceDictionary.
The source for ThemeResourceDictionary is available on my original blog post, but it's also on a different Stack Overflow question in case my blog ever implodes.
You will have to manage brushes you apply to your elements yourself from code. Currently, I have found this to be the only way of adjusting to a different PhoneBackgroundColor.
For example:
In xaml
<TextBlock Text="Some text" Foreground="{Binding VariableTextColor}"/>
In code
var backgroundColor = (System.Windows.Media.Color)Application.Current.Resources["PhoneBackgroundColor"];
if(backgroundColor == "#FF000000") //Dark theme selected
VariableTextColor = RedBrush;
else
VariableTextColor = WhiteBrush;
Another aproach using PhoneDarkThemeVisibility resource:
/// <summary>
/// Determines if the application is running in the dark theme
/// </summary>
private bool IsDarkTheme
{
get
{
if (IsDesignMode)
{
return true;
}
else
{
return (Visibility)Application.Current
.Resources["PhoneDarkThemeVisibility"] == Visibility.Visible;
}
}
}
The approach described by Richard Szalay didn't work in my case either because I needed it in a UserControl or because of something else, however it is really close to the answer. My project is Silverlight Windows Phone 8.1 project and I'm using Visual Studio 2013 with the latest updates. Probably that was the problem. This is what helped in my case
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Application;component/Controls/NumberKeyboard.Dark.xaml" />
<windows:ThemeSelector>
<windows:ThemeSelector.Dark>
<ResourceDictionary Source="/Application;component/Controls/NumberKeyboard.Dark.xaml" />
</windows:ThemeSelector.Dark>
<windows:ThemeSelector.Light>
<ResourceDictionary Source="/Application;component/Controls/NumberKeyboard.Light.xaml" />
</windows:ThemeSelector.Light>
</windows:ThemeSelector>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="KeyboardButton" TargetType="controls:SimpleButton">
<Setter Property="FontSize" Value="45" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style x:Key="NumberButton" TargetType="controls:SimpleButton" BasedOn="{StaticResource KeyboardButton}">
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" />
<Setter Property="Background" Value="{StaticResource ButtonBrush}" />
</Style>
<Style x:Key="ControlButton" TargetType="controls:SimpleButton" BasedOn="{StaticResource KeyboardButton}">
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" />
<Setter Property="Background" Value="{StaticResource ButtonBrush}" />
</Style>
<Style x:Key="ActionButton" TargetType="controls:SimpleButton" BasedOn="{StaticResource KeyboardButton}">
<Setter Property="Foreground" Value="{StaticResource PhoneBackgroundBrush}" />
<Setter Property="Background" Value="{StaticResource PhoneForegroundBrush}" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
The key line is <ResourceDictionary Source="/Application;component/Controls/NumberKeyboard.Dark.xaml" /> right before <windows:ThemeSelector>. That line is necessary for the VS designer to display everything right and ThemeSelector overrides the styles in runtime accordingly to the current theme dark or light.

XAML Combine styles going beyond BasedOn?

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>

Resources