So, I have this issue with making a custom treeview with custom treeviewitem:s where the ItemContainerStyle gets cleared by loading the style from the custom style.
It work like this. I have custom MyTreeViewItem based on TreeViewItem.
<TreeViewItem x:Class="UI.MyTreeViewItem"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<TreeViewItem.Resources>
<Style x:Key="MyTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="Background" Value="#AEFFC1" />
</Style>
</TreeViewItem.Resources>
</TreeViewItem>
As you can see I have just have a simple coloring here just to make sure that the styling it self works. This how ever wont load unless I do like this in code behind.
EDIT: I know things like coloring don't need to be put here as there was intended to be a template here instead. How ever since noting really worked, I just stripped this down to the bones to make sure I put something super simple in that I know should work in case it was becaouse of the template it self.
public partial class MyTreeViewItem : TreeViewItem
{
public MyTreeViewItem()
{
InitializeComponent();
this.Loaded += MyTreeViewItem_Loaded;
}
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Style = Resources["MyTreeViewItemStyle"] as Style;
}
}
This works grate. Have used this several other times with other controls to have custom styling for controls needed to be loaded up with out having to bother to "restyle" everything over and over again.
How ever there are a issue I come across with this. And that is when ItemContainerStyle is being used of this kind of custom styled controller.
<local:BaseTreeView x:Class="My.Navigator.NavigatorTreeView"
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:ui="clr-namespace:UI;assembly=BaseCode"
d:DesignHeight="450" d:DesignWidth="800">
<ui:MyTreeView ItemsSource="{Binding Path=Nodes}">
...
<ui:MyTreeView.ItemContainerStyle>
<Style TargetType="{x:Type ui:MyTreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="Selected" Handler="TreeView_SelectedItemChanged" />
<EventSetter Event="Expanded" Handler="TreeView_NodeExpanded" />
<EventSetter Event="Collapsed" Handler="TreeView_NodeCollapsed" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ui:MyTreeView.ItemContainerStyle>
</ui:MyTreeView>
</local:BaseTreeView>
This ui:MyTreeView.ItemContainerStyle as you see above gets totally ignored once the style is loaded, by this.Style = Resources["MyTreeViewItemStyle"] as Style; in the MyTreeViewItem_Loaded.
That means these Setters, EventSetters and Triggers will not fire at all, as they still are needed to be able to be added as as additional rules.
How can this be solved, so that the predefined styling in the custom control can be loaded and by using this control, you can still can hook up unique rules, like above with out having the the predefined overrule them?
It's not really clear why you are doing what you are doing. All I can say is that you are overwriting the Style value by assigning it explicitly after the control was initialized with the value from the TreeView.ItemContainerStyle.
Normally, on a UserControl, you would set the properties locally on the element:
<TreeViewItem x:Class="UI.MyTreeViewItem"
...
d:DesignHeight="450" d:DesignWidth="800"
Background="#AEFFC1">
</TreeViewItem>
or in code-behind:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Background =
new SolidColorBrush(ColorConverter.ConvertFromString("#AEFFC1"));
}
When writing a custom Control, you would provide a default Style in Generic.xaml. This is the best solution as it allows the control to be styled (where custom styles are allowed to override default values provided by the default Style). External styles are merged implicitly. You should prefer a custom Control over a UserControl.
Your current code does not allow styling as you forcefully override values provided by a custom Style:
// Overwrite previous property value.
this.Style = someValue;
This is programming 101, first grade: an assignment always overwrites the old value (reference) of the variable.
Assumming that you are knowing what you are doing and you don't want to use one of the above solution, you must manually merge both styles using the Style.BasedOn property:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
var defaultStyle = Resources["MyTreeViewItemStyle"] as Style;
defaultStyle.BasedOn = this.ItemContainerStyle;
this.Style = defaultStyle;
}
See: Control authoring overview: Models for Control Authoring
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.
I think the simple answer is that you can't do (at least in this version of WPF) what you are trying to do.
That is, for any particular element only one Style can be applied.
However, as others have stated above, maybe you can use BasedOn to 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.
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>
Note:
One thing in particular to note. If you change the TargetType in 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.
<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>
Bea Stollnitz had a good blog post about using a markup extension for this, under the heading "How can I set multiple styles in 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:
<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.
This feature has been around in WPF for a long time, and it’s new in
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 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.
Others
have 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.
[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:
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:
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:
<Button Style="{local:MultiStyle BigButtonStyle GreenButtonStyle}" … />
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.
I started by creating an extension method for Style that knows how to
merge two styles. The code for this method is quite simple:
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.
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:
<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.
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:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Now we have all the pieces needed to write the ProvideValue method:
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:
<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" />
But you can extend from another.. take a look at the BasedOn property
<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>
WPF/XAML doesn't provide this functionality natively, but it does provide the extensibility to allow you to do what you want.
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).
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.,
<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/
This is possible by creating a helper class to use and wrap your styles. CompoundStyle mentioned here shows how to do it. There are multiple ways, but the easiest is to do the following:
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
Hope that helps.
Use AttachedProperty to set multiple styles like following code:
public static 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;
}
}
Usage: (Point the xmlns:local="clr-namespace:style_a_class_like_css" to the right namespace)
<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:
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.
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
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>
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.
When you override SelectStyle you can get GroupBy property via reflection like below:
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;
}
If you are trying to apply a unique style to just one single element as an addition to a base style, there is a completely different way to do this that is IMHO much better for readable and maintainable code.
It's extremely common to need to tweak parameters per individual element. Defining dictionary styles just for use on one-element is extremely cumbersome to maintain or make sense of. To avoid creating styles just for one-off element tweaks, read my answer to my own question here here:
https://stackoverflow.com/a/54497665/1402498
I'm having a problem when changing a flow direction in a usercontrol by code, it changes all controls flowdirection.
How do i avoid some of thouse controls of changing flowdirection, maintaining allways LTR flowdirection.
Best Regards
You can declare default style in your resource for each and every control, so that it gets applied to respective control by default in you user control.
the xaml code is:
<Window.Resources>
<Style x:Key="TextBoxTemplate" TargetType="{x:Type TextBox}">
<Setter Property="FlowDirection" Value="RightToLeft"/>
</Style>
</Window.Resources>
the code behind is:
public void Init_Style()
{
Style style = this.FindResource("TextBoxTemplate") as Style;
textBox1.Style = style;
}
In this way you can avoid changes of all controls flow direction.
I'm rather new to WPF and encountered some difficulties with user controls.
Please consider the following scenario:
I have a WPF application with a user control, say
MySpecialButtonControl
This "button" has two appearances "oldStyle" and "newStyle" (specified by the Enum "AppearanceStyle") which are controlled by a dependency property with the name
MyLayoutProperty
The callback function has to carry out the code which changes the layout.
Now here is what I would like to do:
I need to change the appearance of all (!) instances of the user control in this window at once in a code-behind file at run-time.
Binding (e.g.) a property to individual instances of the UC like
Binding binding = new Binding("AppearanceStyle");
binding.Source = myOptionsClass;
this.myButton.SetBinding(UserControls.MySpecialButtonControl.MyLayoutProperty, binding);
works perfectly well. But how can I directly change the dependency property for ALL UC instances without having to iterate over collections of the UCs, etc.? Is there even a way to achieve this in WPF/C#?
I tried to solve this problem by using styles, but changing the style which is shared by all UCs itself at runtime is not possible since it is already in use (and the UCs which use this style have already been drawn).
Next, I tried to use a dynamic resource in the style like this:
<uc:MySpecialButtonControl x:Key="myFakeButton" ></uc:MySpecialButtonControl >
<Style x:Key="myButtonStyle" TargetType="uc:MySpecialButtonControl ">
<Setter Property="MyLayoutProperty" Value="{DynamicResource myFakeButton}"></Setter>
</Style>
This allows me to change the "MyLayoutProperty" for "myFakeButton" at runtime which is half of what I want, but even after googling for some time I still could not find a way to bind the "MyLayoutProperty" of "myFakeButton" to the setter which is what I really need.
Any help would be greatly appreciated!
Update:
I tried to implement the solution provided by Michael, but unfortunately, I got the following exception:
PropertyMetadata is already registered for type 'MySpecialButtonControl'.
After some googling (see MSDN) I found that the OverrideMetadata call should be placed in a static constructor of "MySpecialButtonControl" which I did:
static MySpecialButtonControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MySpecialButtonControl),
new FrameworkPropertyMetadata(typeof(MySpecialButtonControl)));
}
Now, the application compiles. And now it works perfectly.
I'm not entirely sure I follow, but I'll attempt an answer. Please comment if this is close, and I'll edit until we get there.
All controls in WPF have a property DefaultStyleKey. Any derived custom control or user control can use this property to set the key of the default style. At runtime, the framework will try to find a resource of this key. It is common to set the default style key equal to the runtime type of the class.
public MySpecialButtonControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof (MySpecialButtonControl),
new FrameworkPropertyMetadata(typeof (MySpecialButtonControl)));
InitializeComponent();
}
When a control placed onto a Window, the framework will look in the available resources for a resource with the key that is defined by DefaultStyleKey. The resource can be defined in a number of places. Google "WPF resource resolution" for more info. The simplest way to illustrate is to show the default style defined in your App.xaml.
<Application.Resources>
<!-- the default style for MySpecialButtonControls -->
<Style x:Key="{x:Type uc:MySpecialButtonControl}"
TargetType="{x:Type uc:MySpecialButtonControl}"
BasedOn="{StaticResource {x:Type UserControl}}" >
<Setter Property="Background" Value="Blue" />
</Style>
</Application.Resources>
Now suppose you have two different styles that you want to switch between at runtime. You might define those styles in your App.xaml.
<Application.Resources>
<!-- the first style -->
<Style x:Key="Style1"
TargetType="{x:Type uc:MySpecialButtonControl}"
BasedOn="{StaticResource {x:Type UserControl}}">
<Setter Property="Background" Value="Blue" />
</Style>
<!-- the second style -->
<Style x:Key="Style2"
TargetType="{x:Type uc:MySpecialButtonControl}"
BasedOn="{StaticResource {x:Type UserControl}}">
<Setter Property="Background" Value="Red" />
</Style>
<!-- the default style, now based on Style1 -->
<Style x:Key="{x:Type uc:MySpecialButtonControl}"
TargetType="{x:Type uc:MySpecialButtonControl}"
BasedOn="{StaticResource Style1}" />
</Application.Resources>
At runtime, you could do something like this to toggle the default style of the controls.
private void Button_Click(object sender, RoutedEventArgs e)
{
// get the style resources
var style1 = FindResource("Style1") as Style;
var style2 = FindResource("Style2") as Style;
var defaultStyle = FindResource(typeof (MySpecialButtonControl)) as Style;
if (style1 == null || style2 == null || defaultStyle == null)
return;
// create a new style based on the "other" style
var newDefaultStyle = new Style(
typeof (MySpecialButtonControl),
(defaultStyle.BasedOn == style1 ? style2 : style1));
// set the application-level resource to the new default style
Application.Current.Resources[typeof (MySpecialButtonControl)] = newDefaultStyle;
}
Is this even close?
I've created an AddressInput control for users to enter an address. The control will have a different look depending on where it's used, so I provided a DataTemplate property called AddressTemplate.
The default style looks like this:
<Style TargetType="{x:Type addressUI:AddressInput}">
<Setter Property="AddressTemplate"
Value="{StaticResource DefaultAddressTemplate}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type addressUI:AddressInput}">
<GroupBox Header="Address">
<ContentPresenter ContentTemplate="{Binding Path=AddressTemplate, RelativeSource={RelativeSource TemplatedParent}}"
Content="{Binding Path=Address, RelativeSource={RelativeSource TemplatedParent}}"
x:Name="PART_AddressPresenter" />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
All of my address data templates will contain a combo box for selecting the country (named "PART_CountriesList"). I need to have some code-behind action that fires when the selection changes, which means I need to hook the SelectionChanged event. Inside my AddressInput I need to find PART_CountriesList in the AddressTemplate.
I can get the "PART_AddressPresenter" ContentPresenter like this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var addressPresenter = Template.FindName("PART_AddressPresenter", this) as ContentPresenter;
}
Now how do I get "PART_CountriesList" contained inside AddressTemplate?
I tried this:
var countriesList = AddressTemplate.FindName("PART_CountriesList", addressPresenter);
An exception is thrown because addressPresenter hasn't had its template applied yet. I know ContentPresenter has the OnApplyTemplate override, but it seems silly to extend it for this use.
I suppose if I were to extend ContentPresenter, I'd make a new reusable version that fires an event whenever the OnApplyTemplate method executes. This would probably solve my problem, but it seems crazy. Is there a better way?
I'm curious if someone has the "right" way to do this, but with FindName I always end up resorting to something like this:
Dispatcher.BeginInvoke( new Action(() =>
{
// Call FindName here
}), System.Windows.Threading.DispatcherPriority.Render );
This can cause flicker though because you're waiting until after the data templating is done and rendered to go exercise your code, so if what you're trying to do affects the look of the control this isn't always a great option.