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


119

我有一个用于Label的工具提示,我希望它保持打开状态,直到用户将鼠标移到其他控件上为止。

我已经在工具提示上尝试了以下属性:

StaysOpen="True"

ToolTipService.ShowDuration = "60000"

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

为什么这些值被忽略?


该属性在某处强制执行一个最大值ShowDuration,认为它类似于30,000。大于此值的值将默认恢复为5000
丹尼斯

2
@Dennis:我使用WPF 3.5对此进行了测试并ToolTipService.ShowDuration="60000"工作。它没有默认返回到5000
M. Dudley

@emddudley:ToolTip是否实际上保持打开状态60000ms?您可以将ToolTipService.ShowDuration属性设置为> = 0的任何值(对于Int32.MaxValue),但是工具提示在该长度下不会保持打开状态。
丹尼斯

2
@丹尼斯:是的,它保持打开状态正好60秒钟。这是在Windows 7
M.达德利

@emddudley:可能有所不同。这是我针对Windows XP进行开发时的知识。
丹尼斯,

Answers:


113

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

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

这是唯一对我有用的解决方案。如何修改此代码以将Placement属性设置为Top?new FrameworkPropertyMetadata("Top")不起作用。
InvalidBrainException 2014年

6
我已将其标记为正确答案(在将近6年后,对不起),因为它实际上在所有受支持的Windows版本上都有效,并且可以将其打开49天,这应该足够长了:p
TimothyP 2015年

1
我也将其放在Window_Loaded事件中,效果很好。唯一要做的就是确保摆脱在XAML中设置的任何“ ToolTipService.ShowDuration”,在XAML中设置的持续时间将覆盖此代码尝试实现的行为。感谢John Whiter提供的解决方案。
nicko

1
FWIW,我之所以喜欢这一点,恰恰是因为它是全球性的-我希望我的应用程序中的所有工具提示能够持续更长的时间,而无需大张旗鼓。这样做仍然可以使您有选择地在上下文特定的位置选择性地应用较小的值,就像在其他答案中一样。(但一如既往,这如果是申请才有效-如果你正在写一个控件库,还是其他什么东西,那么你必须只使用上下文特定的解决方案;全局状态是不是你一起玩。)
Miral

1
这可能很危险!在设置执行两次计算的计时器间隔时,Wpf在内部使用TimeSpan.FromMilliseconds()。这意味着当使用Interval属性将该值应用于计时器时,可以获取ArgumentOutOfRangeException。
一月

190

TooltipService.ShowDuration 可以,但是必须在具有工具提示的对象上进行设置,如下所示:

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

我说选择这个设计是因为它允许相同的工具提示在不同的控件上具有不同的超时时间。


4
它还允许您直接指定内容ToolTip,而无需显式<ToolTip>,这可以使绑定更简单。
svick

15
这应该是选择的答案,因为它是特定于上下文的而不是全局的。
弗拉德

8
持续时间以毫秒为单位。默认值为5000。上面的代码指定12秒。
Contango

1
如果您将相同的工具提示实例与多个控件一起使用,则迟早会出现“另一个父级的已有视觉子级”异常。
springy76 '16

1
这应该是正确答案的主要原因是,这是出于实际高级编程的精神,直接进入XAML代码,并且容易注意到。另一个解决方案除了具有全局性之外,还有些古怪和冗长。我敢打赌,大多数使用它的人都会忘记他们一周内的工作方式。
j riv

15

今晚这也使我发疯。我创建了一个ToolTip子类来处理该问题。对我来说,在.NET 4.0上,该ToolTip.StaysOpen属性不是“真正”保持打开状态。

在下面的类中,使用new属性ToolTipEx.IsReallyOpen而不是property 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);
        }
    }
}

小问题:Microsoft为什么不将DependencyProperty属性(获取程序/设置程序)虚拟化,以便我们可以接受/拒绝/调整子类中的更改?还是virtual OnXYZPropertyChanged每个都做一个DependencyProperty?啊。

- -编辑 - -

我上面的解决方案在XAML编辑器中看起来很奇怪-工具提示始终显示,从而阻止了Visual Studio中的某些文本!

这是解决此问题的更好方法:

一些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。两者都有管理某些属性的“服务”类(例如ToolTipService和)ContextMenuService,并且Popup在显示期间均用作“秘密”父控件。最后,我注意到Web上的所有 XAML ToolTip示例都没有ToolTip直接使用类。相反,它们嵌入一个StackPanel带有TextBlock秒。你说的话:“嗯...”


1
仅仅因为答案透彻,您应该获得更多的选票。向我+1。
Hannish 2013年

ToolTipService需要放置在父元素上,请参见上面的Martin Konicek的答案。
Jeson Martajaya '19

8

您可能想使用Popup而不是Tooltip,因为Tooltip假定您以预定义的UI标准方式使用它。

我不确定为什么StaysOpen不起作用,但是ShowDuration可以按MSDN中的文档进行工作-这是显示工具提示时显示的时间。将其设置为少量(例如500毫秒)以查看差异。

您的诀窍是维持“上一个悬停的控件”状态,但是一旦有了,一旦使用一个Popup,动态地(手动或通过绑定)更改放置目标和内容应该是相当简单的,或隐藏最后一个可见的Popup(如果您使用多个)。

调整窗口大小和移动窗口时,弹出窗口有些陷阱(弹出窗口不随容器移动),因此在调整行为时,您可能还需要牢记这一点。有关更多详细信息,请参见此链接

HTH。


3
另外请注意,弹出窗口始终位于所有桌面对象的顶部-即使您切换到另一个程序,该弹出窗口也将是可见的,并且会掩盖另一个程序的一部分。
杰夫·B

这就是为什么我不喜欢使用弹出窗口的原因。...因为它们不会随程序一起收缩,并且会停留在所有其他程序的顶部。此外,默认情况下,调整/移动主应用程序的大小不会随弹出窗口一起移动。
雷切尔

FWIW,这个UI约定无论如何都是垃圾。没有什么比在我阅读时消失的工具提示更令人讨厌的。
罗曼·斯塔科夫

7

如果你想指定你的,只有某些元素Window有有效的无限期ToolTip持续时间您可以定义一个Style在你的Window.Resources这些元素。下面是一个StyleButton有这样的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.ResourcesStyle更改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>

注意:执行此操作时,我还使用BasedOn了,Style因此ToolTip将应用为自定义控件的普通版本定义的所有其他内容。


5

前几天我只是在与WPF工具提示搏斗。似乎无法阻止它单独出现和消失,因此最终我还是采取了处理该Opened事件的措施。例如,除非它有一些内容,否则我想停止它的打开,因此我处理了该Opened事件,然后执行了以下操作:

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

这是一个hack,但是有效。

大概您可以类似地处理该Closed事件并告诉它再次打开,从而使其可见。


ToolTip有一个名为HasContent的属性,您可以代替使用
benPearce 2010年


0

另外,如果您想在工具提示中放置任何其他控件,则由于工具提示本身可以获得焦点,因此它将无法聚焦。因此,就像米卡丹(micahtan)所说,您最好的镜头是弹出式视窗。


0

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

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


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

它为我工作。将此行复制到您的类构造函数中。


3
这是具有最高投票数的已接受答案的复制和粘贴
CodingYourLife 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.