Erzwingen, dass ein WPF-Tooltip auf dem Bildschirm angezeigt wird

119

Ich habe einen Tooltip für ein Etikett und möchte, dass es geöffnet bleibt, bis der Benutzer die Maus auf ein anderes Steuerelement bewegt.

Ich habe die folgenden Eigenschaften im Tooltip ausprobiert:

StaysOpen="True"

und

ToolTipService.ShowDuration = "60000"

In beiden Fällen wird der Tooltip jedoch nur genau 5 Sekunden lang angezeigt.

Warum werden diese Werte ignoriert?

TimothyP
quelle
Es gibt irgendwo einen Maximalwert für die ShowDurationEigenschaft, denken Sie, es ist so etwas wie 30,000. Alles, was größer ist, wird standardmäßig wieder verwendet 5000.
Dennis
2
@ Tennis: Ich habe dies mit WPF 3.5 getestet und ToolTipService.ShowDuration="60000"gearbeitet. Es wurde nicht standardmäßig zurückgesetzt 5000.
M. Dudley
@emddudley: Bleibt der ToolTip tatsächlich 60000 ms lang geöffnet? Sie können die ToolTipService.ShowDurationEigenschaft auf einen beliebigen Wert> = 0 (auf Int32.MaxValue) setzen, der Tooltip bleibt jedoch für diese Länge nicht geöffnet.
Dennis
2
@ Tennis: Ja, es blieb genau 60 Sekunden offen. Dies ist unter Windows 7.
M. Dudley
@ Emddudley: Das könnte der Unterschied sein. Dies war das Wissen aus der Zeit, als ich gegen Windows XP entwickelte.
Dennis

Antworten:

113

Fügen Sie diesen Code einfach in den Initialisierungsabschnitt ein.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
John Whiter
quelle
Dies war die einzige Lösung, die für mich funktioniert hat. Wie können Sie diesen Code anpassen, um die Placement-Eigenschaft auf Top zu setzen? new FrameworkPropertyMetadata("Top")funktioniert nicht
InvalidBrainException
6
Ich habe dies (nach fast 6 Jahren, sorry) als die richtige Antwort markiert, da dies tatsächlich auf allen unterstützten Windows-Versionen funktioniert und 49 Tage lang geöffnet bleibt, was lang genug sein sollte: p
TimothyP
1
Ich habe dies auch in mein Window_Loaded-Ereignis eingefügt und es funktioniert großartig. Das einzige, was Sie tun müssen, ist sicherzustellen, dass Sie alle in Ihrer XAML festgelegten "ToolTipService.ShowDuration" entfernen. Die in XAML festgelegten Dauern überschreiben das Verhalten, das dieser Code erreichen möchte. Vielen Dank an John Whiter für die Bereitstellung der Lösung.
Nicko
1
FWIW, ich bevorzuge dieses, gerade weil es global ist - ich möchte, dass alle Tooltips in meiner App ohne weitere Fanfare länger bestehen bleiben. Auf diese Weise können Sie auch an kontextspezifischen Stellen selektiv einen kleineren Wert anwenden, genau wie in der anderen Antwort. (Wie immer gilt dies jedoch nur, wenn Sie die Anwendung sind. Wenn Sie eine Steuerungsbibliothek oder etwas anderes schreiben, müssen Sie nur kontextspezifische Lösungen verwenden. Mit dem globalen Status können Sie nicht spielen.)
Miral
1
Das kann gefährlich sein! Wpf verwendet intern TimeSpan.FromMilliseconds (), wenn das Timer-Intervall festgelegt wird, das doppelte Berechnungen durchführt. Dies bedeutet, dass Sie ArgumentOutOfRangeException erhalten können, wenn der Wert mithilfe der Interval-Eigenschaft auf den Timer angewendet wird.
Januar
190

TooltipService.ShowDuration funktioniert, aber Sie müssen es für das Objekt mit dem Tooltip wie folgt festlegen:

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

Ich würde sagen, dass dieses Design gewählt wurde, weil es denselben Tooltip mit unterschiedlichen Zeitüberschreitungen auf verschiedenen Steuerelementen ermöglicht.

Martin Konicek
quelle
4
Außerdem können Sie den Inhalt des ToolTipdirekt ohne explizite Angabe angeben <ToolTip>, was die Bindung vereinfachen kann.
Svick
15
Dies sollte die gewählte Antwort sein, da sie kontextspezifisch und nicht global ist.
Vlad
8
Die Dauer ist in Millisekunden. Der Standardwert ist 5000. Der obige Code gibt 12 Sekunden an.
Contango
1
Wenn Sie dieselbe Tooltip-Instanz mit mehreren Steuerelementen verwenden, wird früher oder später die Ausnahme "bereits visuelles Kind eines anderen übergeordneten Elements" angezeigt.
springy76
1
Der Hauptgrund, warum dies die richtige Antwort sein sollte, ist, dass es im Sinne einer tatsächlichen High-Level-Programmierung ist, direkt in den XAML-Code integriert und leicht zu bemerken ist. Die andere Lösung ist ein bisschen hackig und ausführlich, abgesehen davon, dass es global ist. Ich wette, die meisten Leute, die das benutzt haben, haben vergessen, wie sie es in einer Woche gemacht haben.
j riv
15

Das hat mich heute Abend auch verrückt gemacht. Ich habe eine ToolTipUnterklasse erstellt, um das Problem zu beheben. Für mich unter .NET 4.0 ToolTip.StaysOpenbleibt die Eigenschaft nicht "wirklich" offen.

Verwenden Sie in der folgenden Klasse die neue Eigenschaft ToolTipEx.IsReallyOpenanstelle der Eigenschaft ToolTip.IsOpen. Sie erhalten die Kontrolle, die Sie wollen. Über den Debug.Print()Aufruf können Sie im Debugger-Ausgabefenster sehen, wie oft this.IsOpen = falseaufgerufen wird! Soviel zu StaysOpenoder soll ich sagen "StaysOpen"? Genießen.

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

Kleiner Scherz: Warum hat Microsoft DependencyPropertyEigenschaften (Getter / Setter) nicht virtuell gemacht, damit wir Änderungen in Unterklassen akzeptieren / ablehnen / anpassen können? Oder virtual OnXYZPropertyChangedfür jeden etwas machen DependencyProperty? Pfui.

---Bearbeiten---

Meine obige Lösung sieht im XAML-Editor seltsam aus - der Tooltip wird immer angezeigt und blockiert Text in Visual Studio!

Hier ist ein besserer Weg, um dieses Problem zu lösen:

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

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

Fazit: Etwas ist anders an Klassen ToolTipund ContextMenu. Beide haben "Service" -Klassen wie ToolTipServiceund ContextMenuService, die bestimmte Eigenschaften verwalten, und beide Popupwerden während der Anzeige als "geheimes" übergeordnetes Steuerelement verwendet. Schließlich habe ich festgestellt, dass ALLE XAML ToolTip-Beispiele im Web die Klasse nicht ToolTipdirekt verwenden. Stattdessen binden sie ein StackPanelmit TextBlocks ein. Dinge, die dich sagen lassen: "hmmm ..."

Kevinarpe
quelle
1
Sie sollten mehr Stimmen für Ihre Antwort erhalten, nur wegen ihrer Gründlichkeit. +1 von mir.
Hannish
ToolTipService muss auf dem übergeordneten Element platziert werden (siehe Antwort von Martin Konicek oben).
Jeson Martajaya
8

Sie möchten wahrscheinlich Popup anstelle von Tooltip verwenden, da Tooltip davon ausgeht, dass Sie es auf vordefinierte Weise für UI-Standards verwenden.

Ich bin nicht sicher, warum StaysOpen nicht funktioniert, aber ShowDuration funktioniert wie in MSDN dokumentiert - es ist die Zeit, die der Tooltip angezeigt wird, wenn er angezeigt wird. Stellen Sie einen kleinen Wert (z. B. 500 ms) ein, um den Unterschied zu erkennen.

Der Trick in Ihrem Fall besteht darin, den Status "Zuletzt schwebendes Steuerelement" beizubehalten. Sobald Sie dies jedoch getan haben, sollte es ziemlich trivial sein, das Platzierungsziel und den Inhalt dynamisch (entweder manuell oder über Bindung) zu ändern, wenn Sie ein Popup verwenden. oder das letzte sichtbare Popup ausblenden, wenn Sie mehrere verwenden.

Es gibt einige Fallstricke bei Popups, wenn es um die Größenänderung und das Verschieben von Fenstern geht (Popups werden nicht mit den Containern verschoben). Daher sollten Sie dies auch berücksichtigen, während Sie das Verhalten optimieren. Siehe diesen Link für weitere Details.

HTH.

Micahtan
quelle
3
Beachten Sie auch, dass Popups immer über allen Desktop-Objekten angezeigt werden. Selbst wenn Sie zu einem anderen Programm wechseln, ist das Popup sichtbar und verdeckt einen Teil des anderen Programms.
Jeff B
Genau deshalb benutze ich keine Popups ... weil sie nicht mit dem Programm schrumpfen und über alle anderen Programme auf dem Laufenden bleiben. Durch Ändern der Größe / Verschieben der Hauptanwendung wird das Popup standardmäßig nicht verschoben.
Rachel
FWIW, diese UI-Konvention ist sowieso Müll . Nur wenige Dinge sind ärgerlicher als ein Tooltip, der beim Lesen verschwindet.
Roman Starkov
7

Wenn Sie angeben möchten, dass nur bestimmte Elemente in Ihrer Windoweffektiv unbegrenzte ToolTipDauer haben, können Sie Stylein Ihrer Window.Resourcesfür diese Elemente ein definieren. Hier ist ein Stylefür Buttondas hat so ein 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.">
    ...

Man kann auch hinzufügen Style.Resources, Styleum das Erscheinungsbild der angezeigten zu ändern ToolTip, zum Beispiel:

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

Hinweis: Als ich dies tat, verwendete ich auch BasedOnin, Styledamit alles andere, was für die Version meines benutzerdefinierten Steuerelements mit einem Normal definiert wurde ToolTip, angewendet wurde.

Jonathan Allan
quelle
5

Ich habe erst neulich mit dem WPF Tooltip gerungen. Es scheint nicht möglich zu sein, zu verhindern, dass es von selbst erscheint und verschwindet, also habe ich am Ende auf die Behandlung des OpenedEreignisses zurückgegriffen. Zum Beispiel wollte ich verhindern, dass es geöffnet wird, es sei denn, es enthält Inhalte. Deshalb habe ich das OpenedEreignis behandelt und dann Folgendes ausgeführt:

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

Es ist ein Hack, aber es hat funktioniert.

Vermutlich könnten Sie das ClosedEreignis auf ähnliche Weise behandeln und anweisen, es erneut zu öffnen, um es sichtbar zu halten.

Daniel Earwicker
quelle
ToolTip hat eine Eigenschaft namens HasContent, die Sie stattdessen verwenden könnten
benPearce
2

Nur der Vollständigkeit halber: Im Code sieht es so aus:

ToolTipService.SetShowDuration(element, 60000);
Ulrich Beckert
quelle
0

Auch wenn Sie jemals ein anderes Steuerelement in Ihre QuickInfo einfügen möchten, kann es nicht fokussiert werden, da eine QuickInfo selbst fokussiert werden kann. Wie Micahtan sagte, ist Ihr bester Schuss ein Popup.

Carlo
quelle
0

Habe mein Problem mit dem gleichen Code behoben.

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

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

Es funktioniert für mich. Kopieren Sie diese Zeile in Ihren Klassenkonstruktor.

Vibhuti Sharma
quelle
3
Dies ist das Kopieren und Einfügen einer akzeptierten Antwort mit den zweithöchsten Upvotes
CodingYourLife