Forzare la visualizzazione di una descrizione comando WPF sullo schermo


119

Ho una descrizione comando per un'etichetta e desidero che rimanga aperta finché l'utente non sposta il mouse su un altro controllo.

Ho provato le seguenti proprietà nella descrizione comando:

StaysOpen="True"

e

ToolTipService.ShowDuration = "60000"

Ma in entrambi i casi la descrizione comando viene visualizzata solo per esattamente 5 secondi.

Perché questi valori vengono ignorati?


C'è un valore massimo imposto da qualche parte per la ShowDurationproprietà, pensa che sia qualcosa di simile 30,000. Qualunque cosa maggiore di quello e tornerà automaticamente a 5000.
Dennis

2
@ Dennis: l'ho testato con WPF 3.5 e ho ToolTipService.ShowDuration="60000"funzionato. Non era predefinito per tornare a 5000.
M. Dudley

@emddudley: la descrizione comando rimane effettivamente aperta per 60000 ms? È possibile impostare la ToolTipService.ShowDurationproprietà su qualsiasi valore> = 0 (su Int32.MaxValue), tuttavia la descrizione comando non rimarrà aperta per quella lunghezza.
Dennis

2
@ Dennis: Sì, è rimasto aperto esattamente per 60 secondi. Questo è su Windows 7.
M. Dudley

@emddudley: Questa potrebbe essere la differenza. Questa era la conoscenza di quando stavo sviluppando contro Windows XP.
Dennis

Risposte:


113

Metti semplicemente questo codice nella sezione di inizializzazione.

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

Questa era l'unica soluzione che ha funzionato per me. Come puoi adattare questo codice per impostare la proprietà Placement su Top? new FrameworkPropertyMetadata("Top")non funziona.
InvalidBrainException

6
L'ho contrassegnata (dopo quasi 6 anni anni, scusa) come la risposta corretta perché funziona effettivamente su tutte le versioni supportate di Windows e la tiene aperta per 49 giorni, il che dovrebbe essere abbastanza a lungo: p
TimothyP

1
L'ho anche inserito nel mio evento Window_Loaded e funziona alla grande. L'unica cosa che devi fare è assicurarti di eliminare qualsiasi "ToolTipService.ShowDuration" che hai impostato nel tuo XAML, le durate che hai impostato in XAML sovrascriveranno il comportamento che questo codice sta cercando di ottenere. Grazie a John Whiter per aver fornito la soluzione.
nicko

1
FWIW, preferisco questo proprio perché è globale: voglio che tutti i suggerimenti nella mia app persistano più a lungo senza ulteriori fanfare. In questo modo è comunque possibile applicare selettivamente un valore inferiore anche in luoghi specifici del contesto, esattamente come nell'altra risposta. (Ma come sempre, questo è valido solo se sei l'applicazione: se stai scrivendo una libreria di controllo o qualcos'altro, devi usare solo soluzioni specifiche del contesto; lo stato globale non è tuo con cui giocare.)
Miral

1
Questo può essere pericoloso! Wpf utilizza internamente TimeSpan.FromMilliseconds () quando imposta l'intervallo del timer che esegue doppi calcoli. Ciò significa che quando il valore viene applicato al timer utilizzando la proprietà Interval è possibile ottenere ArgumentOutOfRangeException.
gennaio

191

TooltipService.ShowDuration funziona, ma devi impostarlo sull'oggetto che ha il Tooltip, in questo modo:

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

Direi che questo design è stato scelto perché consente lo stesso suggerimento con timeout diversi su controlli diversi.


4
Consente inoltre di specificare il contenuto ToolTipdirettamente, senza esplicito <ToolTip>, che può semplificare l'associazione.
svick

15
Questa dovrebbe essere la risposta scelta in quanto è specifica del contesto e non globale.
Vlad

8
La durata è in millisecondi. Il valore predefinito è 5000. Il codice precedente specifica 12 secondi.
Contango

1
Se utilizzi la stessa istanza del tooltip con più controlli, presto o tardi otterrai un'eccezione "figlio già visivo di un genitore diverso".
springy76

1
Il motivo principale per cui questa dovrebbe essere la risposta corretta è che è nello spirito dell'attuale programmazione di alto livello, direttamente nel codice XAML e facile da notare. L'altra soluzione è un po 'complicata e prolissa oltre al punto che è globale. Scommetto che la maggior parte delle persone che l'hanno usato si sono dimenticate di come l'hanno fatto in una settimana.
j riv

15

Anche questo mi stava facendo impazzire stasera. Ho creato una ToolTipsottoclasse per gestire il problema. Per me, su .NET 4.0, la ToolTip.StaysOpenproprietà non "realmente" rimane aperta.

Nella classe seguente, usa la nuova proprietà ToolTipEx.IsReallyOpen, invece della proprietà ToolTip.IsOpen. Otterrai il controllo che desideri. Tramite la Debug.Print()chiamata, puoi guardare nella finestra di output del debugger quante volte this.IsOpen = falseviene chiamato! Così tanto per StaysOpen, o dovrei dire "StaysOpen"? Godere.

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

Piccolo sproloquio: perché Microsoft non ha reso le DependencyPropertyproprietà (getter / setters) virtuali in modo da poter accettare / rifiutare / modificare le modifiche nelle sottoclassi? O crearne uno virtual OnXYZPropertyChangedper ognuno DependencyProperty? Ugh.

---Modificare---

La mia soluzione sopra sembra strana nell'editor XAML: il suggerimento è sempre visualizzato, bloccando del testo in Visual Studio!

Ecco un modo migliore per risolvere questo problema:

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

Alcuni codici:

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

Conclusione: c'è qualcosa di diverso nelle classi ToolTipe ContextMenu. Entrambi hanno classi "servizio", come ToolTipServicee ContextMenuService, che gestiscono determinate proprietà, ed entrambi utilizzano Popupcome controllo genitore "segreto" durante la visualizzazione. Infine, ho notato che TUTTI gli esempi di descrizioni comandi XAML sul Web non utilizzano la classe ToolTipdirettamente. Invece, incorporano un StackPanelcon TextBlocks. Cose che ti fanno dire: "hmmm ..."


1
Dovresti ottenere più voti sulla tua risposta solo per la sua completezza. +1 da me.
Hannish

ToolTipService deve essere posizionato sull'elemento genitore, vedere la risposta di Martin Konicek sopra.
Jeson Martajaya

8

Probabilmente vorrai usare Popup invece di Tooltip, poiché Tooltip presume che tu lo stia utilizzando nel modo predefinito dell'interfaccia utente.

Non sono sicuro del motivo per cui StaysOpen non funziona, ma ShowDuration funziona come documentato in MSDN: è la quantità di tempo che il Tooltip viene visualizzato QUANDO viene visualizzato. Impostalo su una piccola quantità (ad esempio 500 msec) per vedere la differenza.

Il trucco nel tuo caso è mantenere lo stato "ultimo controllo al passaggio del mouse", ma una volta ottenuto questo dovrebbe essere abbastanza banale cambiare il target di posizionamento e il contenuto in modo dinamico (manualmente o tramite associazione) se stai utilizzando un popup, o nascondendo l'ultimo Popup visibile se stai usando più.

Ci sono alcuni trucchi con i popup per quanto riguarda il ridimensionamento e lo spostamento della finestra (i popup non si spostano con i contenitori), quindi potresti tenerlo a mente mentre stai modificando il comportamento. Vedi questo collegamento per maggiori dettagli.

HTH.


3
Inoltre, fai attenzione che i popup sono sempre in cima a tutti gli oggetti del desktop: anche se passi a un altro programma, il popup sarà visibile e oscuro come parte dell'altro programma.
Jeff B,

Questo è esattamente il motivo per cui non mi piace usare i popup ... perché non si restringono con il programma e rimangono in cima a tutti gli altri programmi. Inoltre, il ridimensionamento / spostamento dell'applicazione principale non sposta il popup con essa per impostazione predefinita.
Rachel

FWIW, questa convenzione dell'interfaccia utente è comunque spazzatura . Poche cose sono più fastidiose di un tooltip che scompare mentre lo sto leggendo.
Roman Starkov

7

Se vuoi specificare che solo alcuni elementi nel tuo Windowhanno una ToolTipdurata effettivamente indefinita puoi definire un Stylenel tuo Window.Resourcesper quegli elementi. Ecco un Styleper Buttonche ha un tale 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.">
    ...

Si può anche aggiungere Style.Resourcesalla Styleper modificare l'aspetto della ToolTipmostra, ad esempio:

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

Nota: quando ho fatto questo ho usato anche BasedOnnel Stylecosì tutto il resto definito per la versione del mio controllo personalizzato con una normale ToolTipsarebbe stato applicato.


5

Solo l'altro giorno stavo lottando con il tooltip di WPF. Non sembra possibile impedirgli di apparire e scomparire da solo, quindi alla fine sono ricorsi alla gestione Openeddell'evento. Ad esempio, volevo impedirne l'apertura a meno che non avesse dei contenuti, quindi ho gestito l' Openedevento e poi ho fatto questo:

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

È un trucco, ma ha funzionato.

Presumibilmente potresti gestire allo stesso modo l' Closedevento e dirgli di riaprirsi, mantenendolo così visibile.


ToolTip ha una proprietà chiamata HasContent che potresti usare invece
benPearce

2

Solo per completezza: nel codice assomiglia a questo:

ToolTipService.SetShowDuration(element, 60000);

0

Inoltre, se si desidera inserire qualsiasi altro controllo nella descrizione comando, non sarà attivabile poiché una descrizione comandi stessa può ottenere lo stato attivo. Quindi, come ha detto micahtan, il tuo colpo migliore è un Popup.


0

Ho risolto il mio problema con lo stesso codice.

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


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

Sta funzionando per me. Copia questa riga nel costruttore della tua classe.


3
questo è il copia e incolla della risposta accettata con i secondi voti più positivi
CodingYourLife
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.