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
Related
Background
I am developing an application that generates Labels based on data provided in a CSV file. I would like the user to be able to apply Templates to change the appearance of these Labels, I also need the user to be able to Edit and Modify these Templates.
I am deriving these Templates from the Existing Style Class in WPF. Even though I am presenting this to the End user as a 'Template', for the sake of this post, I will refer to them as Styles to avoid confusion with Data Templating.
Due to a Style becoming Sealed after use or after being referenced by another style.BasedOn Property, in order to allow the user to Modify these Styles, for each modification, I need to generate a new Style based on the Current Style. I do this using the BasedOn Property.
Question
What is actually happening internally when the Style.BasedOn property is set and that style is consumed by an element?
My first thought was that a copy of the Setters collection was created and applied to the new Style, but as the following code shows, that is not the case:
var styleA = new Style();
styleA.Setters.Add(new Setter(/* DP and Value */));
var styleB = new Style();
styleB.BasedOn = styleA;
Console.WriteLine(styleA.Setters.Count);
Console.WriteLine(styleB.Setters.Count);
// Ouput.
// 1
// 0
My next thought is that the BasedOn property holds a reference to the style applied to it, and the actual logic is performed by the FrameworkElement.Style OnPropertyChanged handler. I had a look through the Reference Source, but in all honesty, got in over my head pretty quickly.
Any help or suggestions for another way to approach the problem will be greatly appreciated.
As you suggested for another approach, here is one commonly used: resource dictionnaries.
Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="DefaultLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="20" />
</Style>
</ResourceDictionary>
StyleBlue.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource DefaultLabelStyle}" TargetType="Label">
<Setter Property="Background" Value="Blue" />
</Style>
</ResourceDictionary>
StyleRed.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource DefaultLabelStyle}" TargetType="Label">
<Setter Property="Background" Value="Red" />
</Style>
</ResourceDictionary>
Demo
using System;
using System.Windows;
namespace WpfApplication3
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
}
private void SetTheme(string theme)
{
var mergedDictionaries = Resources.MergedDictionaries;
mergedDictionaries.Clear();
var dictionary = new ResourceDictionary {Source = new Uri(theme)};
mergedDictionaries.Add(dictionary);
}
private void ButtonRedTheme_Click(object sender, RoutedEventArgs e)
{
SetTheme(#"pack://application:,,,/Themes/StyleRed.xaml");
}
private void ButtonBlueTheme_Click(object sender, RoutedEventArgs e)
{
SetTheme(#"pack://application:,,,/Themes/StyleBlue.xaml");
}
}
}
As you can see in Style.BasedOn, there's simply no indication of what's happening under the hood, certainly a lot.
However, following is said : Typically, you use the Markup Extensions and WPF
XAML to refer to an existing style.
As an end-user it happens that you simply don't have to know the inner workings as there are simpler patterns for using this feature : XAML / resource dictionaries.
There is plenty of documentation for styling/templates, start by reading this one : Styling and Templating
For your users you could direct them to XamlPad for creating these templates, you'd get real-time preview at the same time.
versed users will know what to do and will be able to
for beginners you can provide a starter pack of templates
Weigh the 'pros' and 'cons' of this solution against using 'CSV' and 'code' approach (extensible only with your involvement and IMO doomed to fail).
EDIT
You can see exactly what's happening in BasedOn by looking at the source code : http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Style.cs,dd312833d0723042
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?
Using the following sample R# (resharper) is not able to find the datacontext of the Row style and warns about a wrong binding (at runtime works fine). Seems like the Style is not getting the DataContext of the ItemsSource:
MainWindow.xaml:
<Window x:Class="TestDatacontext.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:testDatacontext="clr-namespace:TestDatacontext"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance testDatacontext:MainWindowVM}" >
<DataGrid ItemsSource="{Binding Items}" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Window>
MainWindowVM:
using System.Collections.ObjectModel;
namespace TestDatacontext
{
class MainWindowVM
{
public ObservableCollection<ItemVM> Items { get; private set; }
}
}
ItemVM:
namespace TestDatacontext
{
class ItemVM
{
public string Name { get; set; }
}
}
You are correct, ReSharper has no knowledge about how RowStyle will be used in this particular control (is it style per every item of ItemsSource? or some kind of header style and bindings will have access to ItemsSource object itself?), so it stops traversing tree looking for DataContext type on Style declaration.
This issue can be solved with additional annotation on Style declaration:
<Style TargetType="DataGridRow" d:DataContext="{d:DesignInstance vms:ItemVM}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
Project will compile fine, VS designer and R# will work, but VS xaml support will produce 1 error in errors window - "Property 'DataContext' is not attachable to elements of type 'Style'". That's a bit annoying, but works. Other way is to quilify property type like this:
<Style TargetType="DataGridRow">
<Setter Property="Header" Value="{Binding (vms:ItemVM.Name)}" />
</Style>
But it produces VS xaml support error too :) and have slightly different behavior in runtime - this binding will work only with Name property of ItemVM type and will not work if somehow VM object will be replaced with some other object of different type with Name property at runtime (so binding became "strongly-typed").
We are still looking for a better way to solve this kind of problems in ReSharper 8.0 and make VS designer happy, sorry for confusing!
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 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.