强制 WPF 工具提示保留在屏幕上

我有一个标签的工具提示,我希望它保持打开,直到用户 将鼠标移动到另一个控件。

我在工具提示中尝试了以下属性:

StaysOpen="True"

还有

ToolTipService.ShowDuration = "60000"

但是在这两种情况下,工具提示只显示5秒钟。

为什么忽略这些价值观?

86736 次浏览

前几天我还在和 WPF 工具提示做斗争。它似乎不可能阻止它的出现和消失本身,所以最后我诉诸于处理 Opened事件。例如,我想阻止它打开,除非它有一些内容,所以我处理了 Opened事件,然后这样做:

tooltip.IsOpen = (tooltip.Content != null);

虽然是黑客干的,但还是成功了。

假设您可以类似地处理 Closed事件,并告诉它再次打开,从而使其保持可见。

您可能希望使用 Popup 而不是 Tooltip,因为工具提示假设您正在以预定义的 UI 标准方式使用它。

我不知道为什么 stayOpen 不能工作,但是显示持续时间可以在 mSDN 中记录下来——这是 Tooltip 显示的时间。设置为一个小的量(例如500毫秒)来看到差异。

在这种情况下,诀窍是保持“最后一个悬浮控件”的状态,但是一旦你有了这种状态,如果你使用一个弹出窗口,动态地(手动地或者通过绑定)更改位置目标和内容,或者如果你使用多个弹出窗口,隐藏最后一个可见的弹出窗口,这应该是相当微不足道的。

弹出窗口在调整窗口大小和移动方面有一些陷阱(弹出窗口不会移动容器) ,所以在调整行为时,你可能也需要考虑到这一点。有关详细信息,请参阅 这个链接

高温。

此外,如果您想要在工具提示中放入任何其他控件,那么它将无法对焦,因为工具提示本身可以获得焦点。就像 Micahtan 说的,你最好的机会就是弹出窗口。

如果你只想设置一个工具提示,设置 Tooltip 对象的持续时间,像这样:

<Label ToolTipService.ShowDuration="12000" Name="lblShowTooltip" Content="Shows tooltip">
<Label.ToolTip>
<ToolTip>
<TextBlock>Hello world!</TextBlock>
</ToolTip>
</Label.ToolTip>
</Label>

我要说,之所以选择这种设计,是因为它允许在不同控件上使用相同的工具提示和不同的超时。

如果你想在全球范围内使用整个应用程序,请参阅已接受的答案。

只需将此代码放在初始化部分。

ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));

它正在为我工作。将这一行复制到您的类构造函数中。

今晚我也快疯了。我创建了一个 ToolTip子类来处理这个问题。为了我。NET 4.0中,ToolTip.StaysOpen属性不是“真的”保持打开状态。

在下面的类中,使用新的属性 ToolTipEx.IsReallyOpen,而不是属性 ToolTip.IsOpen。你会得到你想要的控制权。通过 Debug.Print()调用,您可以在调试器输出窗口中观察调用 this.IsOpen = false的次数!这么多的 StaysOpen,或者我应该说 "StaysOpen"?好好享受吧。

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);
}
}
}

小小的抱怨: 为什么微软不让 DependencyProperty属性(getters/setter)虚拟化,这样我们就可以接受/拒绝/调整子类中的更改?或者为每个 DependencyProperty制作一个 virtual OnXYZPropertyChanged?呃。

- 编辑

我上面的解决方案在 XAML 编辑器中看起来很奇怪——工具提示总是显示,阻塞了 VisualStudio 中的一些文本!

这里有一个更好的方法来解决这个问题:

一些 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>

一些代码:

// 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);
}
}

结论: ToolTip类和 ContextMenu类有所不同。两者都有“服务”类,如 ToolTipServiceContextMenuService,用于管理某些属性,并且都在显示期间使用 Popup作为“秘密”父控件。最后,我注意到 全部在 Web 上的 XAML 工具提示示例不直接使用类 ToolTip。相反,他们将 StackPanel嵌入到 TextBlock中。让你说: “嗯... ...”

只是为了完整起见: 在代码中它看起来像这样:

ToolTipService.SetShowDuration(element, 60000);

如果要指定 Window中只有某些元素具有 有效地不确定的 ToolTip持续时间,你可以在你的 Window.Resources中为这些元素定义一个 Style。下面是 ButtonStyle,它有这样的 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.">
...

还可以将 Style.Resources添加到 Style中,以改变它显示的 ToolTip的外观,例如:

<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>

注意: 当我这样做的时候,我也在 Style中使用了 BasedOn,所以其他所有为我的定制控件版本定义的正常 ToolTip都将被应用。

用同样的代码解决了我的问题。

ToolTipService.ShowDurationProperty. OverrideMetadata ( Typeof (DependencyObject)、新的 FrameworkPropertyMetadata (Int32.MaxValue) ;