Reputation: 3621
In Eclipse I can focus a ToolTip by pressing F2 while it's open. It converts into a separate Popup, stays open as long as I want and I can resize it.
How can i get this to work in WPF?
Tooltips close themselves after 5 seconds, which makes sense for most of them. I don't tooltips to stay open forever by default, but sometimes I want to read a little longer than 5s. I like the idea of pressing a key to make the ToolTip stick to the GUI until I manually close it.
I hope this is possible with a ToolTip as I don't want to replace every ToolTip in my GUI with a custom Popup behaviour...
Upvotes: 1
Views: 352
Reputation: 7456
Since this gave me a sleepless night i've made an (more or less) simple generic approach to solve this for now and forever.
The following solution is based on Style-Behaviors, which require a bit of preperation sine there is nothing built in.
Make Styles accept Behaviors
/// <summary>
/// Collection for <see cref="StylizedBehaviors"/>
/// </summary>
public class StylizedBehaviorCollection : FreezableCollection<Behavior> {
protected override Freezable CreateInstanceCore() {
return new StylizedBehaviorCollection();
}
}
/// <summary>
/// This Behavior allows us to set Behaviors in Styles for a Generic approach
/// </summary>
public class StylizedBehaviors {
private static readonly DependencyProperty OriginalBehaviorProperty = DependencyProperty.RegisterAttached(@"OriginalBehaviorInternal", typeof(Behavior), typeof(StylizedBehaviors), new UIPropertyMetadata(null));
public static readonly DependencyProperty BehaviorsProperty = DependencyProperty.RegisterAttached(
@"Behaviors",
typeof(StylizedBehaviorCollection),
typeof(StylizedBehaviors),
new FrameworkPropertyMetadata(null, OnPropertyChanged));
public static StylizedBehaviorCollection GetBehaviors(DependencyObject uie) {
return (StylizedBehaviorCollection)uie.GetValue(BehaviorsProperty);
}
public static void SetBehaviors(DependencyObject uie, StylizedBehaviorCollection value) {
uie.SetValue(BehaviorsProperty, value);
}
private static Behavior GetOriginalBehavior(DependencyObject obj) {
return obj.GetValue(OriginalBehaviorProperty) as Behavior;
}
private static int GetIndexOf(BehaviorCollection itemBehaviors, Behavior behavior) {
var index = -1;
var orignalBehavior = GetOriginalBehavior(behavior);
for (var i = 0; i < itemBehaviors.Count; i++) {
var currentBehavior = itemBehaviors[i];
if (currentBehavior == behavior || currentBehavior == orignalBehavior) {
index = i;
break;
}
var currentOrignalBehavior = GetOriginalBehavior(currentBehavior);
if (currentOrignalBehavior != behavior && currentOrignalBehavior != orignalBehavior)
continue;
index = i;
break;
}
return index;
}
private static void OnPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e) {
var uie = dpo as UIElement;
if (uie == null) {
return;
}
var itemBehaviors = Interaction.GetBehaviors(uie);
var newBehaviors = e.NewValue as StylizedBehaviorCollection;
var oldBehaviors = e.OldValue as StylizedBehaviorCollection;
if (newBehaviors == oldBehaviors) {
return;
}
if (oldBehaviors != null) {
foreach (var index in oldBehaviors.Select(behavior => GetIndexOf(itemBehaviors, behavior)).Where(index => index >= 0)) {
itemBehaviors.RemoveAt(index);
}
}
if (newBehaviors == null)
return;
foreach (var behavior in newBehaviors) {
var index = GetIndexOf(itemBehaviors, behavior);
if (index >= 0)
continue;
var clone = (Behavior)behavior.Clone();
SetOriginalBehavior(clone, behavior);
itemBehaviors.Add(clone);
}
}
private static void SetOriginalBehavior(DependencyObject obj, Behavior value) {
obj.SetValue(OriginalBehaviorProperty, value);
}
}
Our desired Hotkey-Behavior
public class ToolTipHotKeyBehavior : Behavior<Control> {
public Key HotKey {
get {
return (Key)this.GetValue(HotKeyProperty);
}
set {
this.SetValue(HotKeyProperty, value);
}
}
public static readonly DependencyProperty HotKeyProperty =
DependencyProperty.Register("HotKey", typeof(Key), typeof(ToolTipHotKeyBehavior), new PropertyMetadata(Key.F1));
protected override void OnAttached() {
this.AssociatedObject.Loaded += this.TargetLoaded;
base.OnAttached();
}
private void TargetLoaded(object sender, RoutedEventArgs e) {
var ctrl = (sender as Control);
if (ctrl == null)
return;
ctrl.PreviewKeyDown += (o, args) => {
if (args.Key != this.HotKey)
return;
if (ctrl.ToolTip.GetType() != typeof(ToolTip))
ToolTipService.SetToolTip(ctrl, new ToolTip { Content = ctrl.ToolTip });
(ctrl.ToolTip as ToolTip).IsOpen = !(ctrl.ToolTip as ToolTip).IsOpen;
};
}
}
Usage with Examples in XAML
<Window.Resources>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="Background" Value="Green"></Setter>
<Setter Property="local:StylizedBehaviors.Behaviors">
<Setter.Value>
<local:StylizedBehaviorCollection>
<local:ToolTipHotKeyBehavior />
</local:StylizedBehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<PasswordBox Margin="10" VerticalAlignment="Center" ToolTip="Enter Password"/>
<Button Content="hello" ToolTip="This is a Button">
<Button.Style>
<Style>
<Style.Setters>
<Setter Property="local:StylizedBehaviors.Behaviors">
<Setter.Value>
<local:StylizedBehaviorCollection>
<local:ToolTipHotKeyBehavior HotKey="F3" />
</local:StylizedBehaviorCollection>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Button.Style>
</Button>
</StackPanel>
Closure
Unfortunately this is probably the most simple and generic approach to perform such actions. As you can see, the Behavior can be applied to everything subclassing Control
.
Furthermore, you can set the Hotkey on each Control / Type of Control differently (Also you could bind it if you want)
Note
This example is based on the fact, you are using strings as initial ToolTips since the Behavior converts it to a real one to be able to open/close it.
I hope this fits your needs. Cheers
Upvotes: 1