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
Related
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
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 need something like this for styles in XAML :
<Application.Resources>
#if DEBUG
<Style TargetType="{x:Type ToolTip}">
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
#else
<Style TargetType="{x:Type ToolTip}">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FlowDirection" Value="RightToLeft"/>
</Style>
#endif
</Application.Resources>
I recently had to do this and was suprised at how simple it was when I couldn't easily find any clear examples. What I did was add the following to AssemblyInfo.cs:
#if DEBUG
[assembly: XmlnsDefinition( "debug-mode", "Namespace" )]
#endif
Then, use the markup-compatability namespace's AlternateContent tag to choose your content based on the presense of that namespace definition:
<Window x:Class="Namespace.Class"
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="debug-mode"
Width="400" Height="400">
...
<mc:AlternateContent>
<mc:Choice Requires="d">
<Style TargetType="{x:Type ToolTip}">
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</mc:Choice>
<mc:Fallback>
<Style TargetType="{x:Type ToolTip}">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FlowDirection" Value="RightToLeft"/>
</Style>
</mc:Fallback>
</mc:AlternateContent>
...
</Window>
Now, when DEBUG is defined, "debug-mode" will also be defined, and the "d" namespace will be present. This makes the AlternateContent tag choose the first block of code. If DEBUG is not defined, the Fallback block of code will be used.
This sample code was not tested, but it's basically the same thing that I'm using in my current project to conditionally show some debug buttons.
I did see a blog post with some example code that relied on the "Ignorable" tag, but that seemed a lot less clear and easy to use as this method.
This is not possible in WPF/Silverlight/WP7.
On an interesting note, the standards document, ISO/IEC 29500 (Office Open XML File Formats), covers how this should be handled in an XML document, and XAML does support one of the items from that spec mc:Ignorable which allows us to do things like this:
<Page xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="Comments"
mc:Ignorable="c">
<Button Content="Some Text"
c:Content="Some other text" />
</Page>
to comment out attributes.
The XAML parser team (SL4, WP7.1, WPF) chose to use that spec to solve their needs for ignoring attributes, rather than just making something up. That is why some of the default XAML pages have the 'mc' namespace defined. I do think it would be cool if XAML one day supported the rest of the spec that allows the loading of alternate content.
The mc:Ignorable attribute is used by Blend to support design time functionality.
You could use a template selector. The DataTemplateSelector class is something you code. With the template selection method that you override, you could put your preprocessor directives.
http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
I wasn't happy with any of these and did this:
In my XAML I put any attributes or tags with a space just so i know I am screwing with them in the .cs file.
<Window x:Class="...
mc:Ignorable="d"
Title=""
BorderThickness="2"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
Height="200"
Width="500"
WindowStyle="None"
>
then in my code behind I do this:
public partial class ScanProgressWindow : Window
{
public ScanProgressWindow()
{
InitializeComponent();
#if DEBUG
this.WindowStyle = WindowStyle.SingleBorderWindow;
#endif
}
}
Works for me.
I feel like the given answers aren't the easiest to use. Here is my solution using a custom attachable dependency property:
using namespace Utility{
public static class DebugVisibility
{
public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.RegisterAttached(
"Debug", typeof(bool?), typeof(DebugVisibility), new PropertyMetadata(default(bool?), IsVisibleChangedCallback));
private static void IsVisibleChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
return;
#if DEBUG
fe.Visibility = Visibility.Visible;
#else
fe.Visibility = Visibility.Collapsed;
#endif
}
public static void SetIsVisible(DependencyObject element, bool? value)
{
element.SetValue(IsVisibleProperty, value);
}
public static bool? GetIsVisible(DependencyObject element)
{
return (bool?)element.GetValue(IsVisibleProperty);
}
}
}
and the xaml would be used like this:
<window ... xmlns:Util="clr-namespace:MyNamespace.Utility" >
<Label Util:DebugVisibility.IsVisible="True">
</window>
I kept it as a bool in case you wanted to add some other visibility logic in there. This is a nice simple toggle that can be bound to and attached to any control
I would like to update the default Window style dynamically at runtime so I can change the FontSize and FontFamily dynamically at runtime. I found that Styles in your resource dictionary are sealed at runtime and cannot be changed, so I used the following method of updating the style:
<Style TargetType="{x:Type Window}">
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontSize" Value="12pt"/>
</Style>
With the following code:
Style newStyle = (Make a copy of the old style but with the FontSize and FontFamily changed)
// Remove and re-add the style to the ResourceDictionary.
this.Resources.Remove(typeof(Window));
this.Resources.Add(typeof(Window), newStyle);
// The style does not update unless you set it on each window.
foreach (Window window in Application.Current.Windows)
{
window.Style = newStyle;
}
There are several problems with this approach and I have a few questions as to why things are the way they are.
Why are styles sealed at runtime and is there a way of making them unsealed?
When I re-add the new style, why is this not picked up by all of my windows? Why do I have to go and manually apply it to every window?
Is there a better way?
I would probably tackle this with a "settings service" which exposes properties for the various settings, and fires INPC as you would for normal binding. Next up I'd change that style to be something like:
<Style x:Key="MyWindowStyle">
<Setter Property="FontFamily" Value="{Binding Path=FontFamily, Source={StaticResource SettingsService}, FallbackValue=Arial}"/>
<Setter Property="FontSize" Value="{Binding Path=FontSize, Source={StaticResource SettingsService}, FallbackValue=12}"/>
</Style>
With your "settings service" defined as a static resource:
<services:SettingsService x:Key="SettingsService"/>
Then in each window make sure the style is set as a DynamicResource:
<Window Style="{DynamicResource MyWindowStyle}" .... >
There is often a lot of misunderstanding around the differences between Static and Dynamic resources, but the basic difference is Static is a "one time" setting whereas Dynamic will update the settings if the resource changes.
Now if you set those properties in your "settings service" they will fire INPC, which will update the Style, which the DynamicResource will pick up on and alter the Window properties accordingly.
Seems like a lot of work, but it gives you some nice flexibility, and all the "heavy lifting" is done purely using Bindings. We use a similar technique on a project I'm working on at the moment so when a user chooses a fill/stroke colour the various tools in the toolbar update to reflect the new values.
I have a tooltip for a Label and I want it to stay open until the user
moves the mouse to a different control.
I have tried the following properties on the tooltip:
StaysOpen="True"
and
ToolTipService.ShowDuration = "60000"
But in both cases the tooltip is only displayed for exactly 5 seconds.
Why are these values being ignored?
If you want to set this for just one tooltip, set the duration on the object having the Tooltip, like this:
<Label ToolTipService.ShowDuration="12000" Name="lblShowTooltip" Content="Shows tooltip">
<Label.ToolTip>
<ToolTip>
<TextBlock>Hello world!</TextBlock>
</ToolTip>
</Label.ToolTip>
</Label>
I'd say that this design was chosen because it allows same tooltip with different timeouts on different controls.
If you want this globally for your whole app, see the accepted answer.
Just put this code in initialization section.
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
This was also driving me crazy tonight. I created a ToolTip subclass to handle the issue. For me, on .NET 4.0, the ToolTip.StaysOpen property is not "really" stays open.
In the class below, use the new property ToolTipEx.IsReallyOpen, instead of property ToolTip.IsOpen. You will get the control you want. Via the Debug.Print() call, you can watch in the debugger Output window just how many times this.IsOpen = false is called! So much for StaysOpen, or should I say "StaysOpen"? Enjoy.
public class ToolTipEx : ToolTip
{
static ToolTipEx()
{
IsReallyOpenProperty =
DependencyProperty.Register(
"IsReallyOpen",
typeof(bool),
typeof(ToolTipEx),
new FrameworkPropertyMetadata(
defaultValue: false,
flags: FrameworkPropertyMetadataOptions.None,
propertyChangedCallback: StaticOnIsReallyOpenedChanged));
}
public static readonly DependencyProperty IsReallyOpenProperty;
protected static void StaticOnIsReallyOpenedChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
ToolTipEx self = (ToolTipEx)o;
self.OnIsReallyOpenedChanged((bool)e.OldValue, (bool)e.NewValue);
}
protected void OnIsReallyOpenedChanged(bool oldValue, bool newValue)
{
this.IsOpen = newValue;
}
public bool IsReallyOpen
{
get
{
bool b = (bool)this.GetValue(IsReallyOpenProperty);
return b;
}
set { this.SetValue(IsReallyOpenProperty, value); }
}
protected override void OnClosed(RoutedEventArgs e)
{
System.Diagnostics.Debug.Print(String.Format(
"OnClosed: IsReallyOpen: {0}, StaysOpen: {1}", this.IsReallyOpen, this.StaysOpen));
if (this.IsReallyOpen && this.StaysOpen)
{
e.Handled = true;
// We cannot set this.IsOpen directly here. Instead, send an event asynchronously.
// DispatcherPriority.Send is the highest priority possible.
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action)(() => this.IsOpen = true),
DispatcherPriority.Send);
}
else
{
base.OnClosed(e);
}
}
}
Small rant: Why didn't Microsoft make DependencyProperty properties (getters/setters) virtual so we can accept/reject/adjust changes in subclasses? Or make a virtual OnXYZPropertyChanged for each and every DependencyProperty? Ugh.
---Edit---
My solution above looks weird in the XAML editor -- the tooltip is always showing, blocking some text in Visual Studio!
Here is a better way to solve this problem:
Some XAML:
<!-- Need to add this at top of your XAML file:
xmlns:System="clr-namespace:System;assembly=mscorlib"
-->
<ToolTip StaysOpen="True" Placement="Bottom" HorizontalOffset="10"
ToolTipService.InitialShowDelay="0" ToolTipService.BetweenShowDelay="0"
ToolTipService.ShowDuration="{x:Static Member=System:Int32.MaxValue}"
>This is my tooltip text.</ToolTip>
Some code:
// Alternatively, you can attach an event listener to FrameworkElement.Loaded
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Be gentle here: If someone creates a (future) subclass or changes your control template,
// you might not have tooltip anymore.
ToolTip toolTip = this.ToolTip as ToolTip;
if (null != toolTip)
{
// If I don't set this explicitly, placement is strange.
toolTip.PlacementTarget = this;
toolTip.Closed += new RoutedEventHandler(OnToolTipClosed);
}
}
protected void OnToolTipClosed(object sender, RoutedEventArgs e)
{
// You may want to add additional focus-related tests here.
if (this.IsKeyboardFocusWithin)
{
// We cannot set this.IsOpen directly here. Instead, send an event asynchronously.
// DispatcherPriority.Send is the highest priority possible.
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action)delegate
{
// Again: Be gentle when using this.ToolTip.
ToolTip toolTip = this.ToolTip as ToolTip;
if (null != toolTip)
{
toolTip.IsOpen = true;
}
},
DispatcherPriority.Send);
}
}
Conclusion: Something is different about classes ToolTip and ContextMenu. Both have "service" classes, like ToolTipService and ContextMenuService, that manage certain properties, and both use Popup as a "secret" parent control during display. Finally, I noticed ALL the XAML ToolTip examples on the Web do not use class ToolTip directly. Instead, they embed a StackPanel with TextBlocks. Things that make you say: "hmmm..."
You probably want to use Popup instead of Tooltip, since Tooltip assumes that you're using it in the pre-defined UI-standards way.
I'm not sure why StaysOpen doesn't work, but ShowDuration works as documented in MSDN -- it's the amount of time the Tooltip is displayed WHEN it's displayed. Set it to a small amount (e.g. 500 msec) to see the difference.
The trick in your case is maintaining the "last hovered control" state, but once you have that it should be fairly trivial to change the placement target and the content dynamically (either manually, or via binding) if you're using one Popup, or hiding the last visible Popup if you're using multiple.
There are some gotchas with Popups as far as Window resizing and moving (Popups don't move w/the containers), so you may want to also have that in mind while you're tweaking the behavior. See this link for more details.
HTH.
If you want to specify that only certain elements in your Window have
effectively indefinite ToolTip duration you can define a Style in your Window.Resources for those elements. Here is a Style for Button that has such a ToolTip :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
...>
...
<Window.Resources>
<Style x:Key="ButtonToolTipIndefinate" TargetType="{x:Type Button}">
<Setter Property="ToolTipService.ShowDuration"
Value="{x:Static Member=sys:Int32.MaxValue}"/>
</Style>
...
</Window.Resources>
...
<Button Style="{DynamicResource ButtonToolTipIndefinate}"
ToolTip="This should stay open"/>
<Button ToolTip="This Should disappear after the default time.">
...
One can also add Style.Resources to the Style to change the appearance of the ToolTip it shows, for example:
<Style x:Key="ButtonToolTipTransparentIndefinate" TargetType="{x:Type Button}">
<Style.Resources>
<Style x:Key="{x:Type ToolTip}" TargetType="{x:Type ToolTip}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="HasDropShadow" Value="False"/>
</Style>
</Style.Resources>
<Setter Property="ToolTipService.ShowDuration"
Value="{x:Static Member=sys:Int32.MaxValue}"/>
</Style>
Note: When I did this I also used BasedOn in the Style so everything else defined for the version of my custom control with a normal ToolTip would be applied.
I was wrestling with the WPF Tooltip only the other day. It doesn't seem to be possible to stop it from appearing and disappearing by itself, so in the end I resorted to handling the Opened event. For example, I wanted to stop it from opening unless it had some content, so I handled the Opened event and then did this:
tooltip.IsOpen = (tooltip.Content != null);
It's a hack, but it worked.
Presumably you could similarly handle the Closed event and tell it to open again, thus keeping it visible.
Just for the sake of completeness:
In code it looks like this:
ToolTipService.SetShowDuration(element, 60000);
Also if you ever want to put any other control in your ToolTip, it won't be focusable since a ToolTip itself can get focus. So Like micahtan said, your best shot is a Popup.
Got my issue fixed with the same code.
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
(almost) Simple XAML version for ALL tooltips:
Put this style in your window resources (and add the missing types you commonly use)
<Style x:Key="LongToolTipStyle" TargetType="FrameworkElement">
<Setter Property="ToolTipService.ShowDuration" Value="20000"/>
</Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Button" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="TextBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="RadioButton" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="CheckBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="ComboBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Grid" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="StackPanel" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="DockPanel" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Image" BasedOn="{StaticResource LongToolTipStyle}"/>
Might be a bit boring, but you can keep it in a resource dictionary.
It's sad that we can't apply these styles to subclasses.
Downside
Whenever you create another style, you must remember to make that style BasedOn="{StaticResource LongToolTipStyle}"
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
It is working for me. Copy this line into your class constructor.