Forzar que una información sobre herramientas de WPF permanezca en la pantalla

119

Tengo una información sobre herramientas para una etiqueta y quiero que permanezca abierta hasta que el usuario mueva el mouse a un control diferente.

He probado las siguientes propiedades en la información sobre herramientas:

StaysOpen="True"

y

ToolTipService.ShowDuration = "60000"

Pero en ambos casos, la información sobre herramientas solo se muestra durante exactamente 5 segundos.

¿Por qué se ignoran estos valores?

TimothyP
fuente
Hay un valor máximo impuesto en algún lugar para la ShowDurationpropiedad, creo que es algo así 30,000. Cualquier cosa mayor que eso y volverá por defecto a 5000.
Dennis
2
@Dennis: Probé esto con WPF 3.5 y funcioné ToolTipService.ShowDuration="60000". No fue por defecto volver a 5000.
M. Dudley
@emddudley: ¿La información sobre herramientas permanece abierta durante 60000 ms? Puede establecer la ToolTipService.ShowDurationpropiedad en cualquier valor> = 0 (en Int32.MaxValue), sin embargo, la información sobre herramientas no permanecerá abierta durante ese tiempo.
Dennis
2
@Dennis: Sí, permaneció abierto durante exactamente 60 segundos. Esto es en Windows 7.
M. Dudley
@emddudley: Esa podría ser la diferencia. Este fue el conocimiento de cuando estaba desarrollando contra Windows XP.
Dennis

Respuestas:

113

Simplemente coloque este código en la sección de inicialización.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
John Whiter
fuente
Esta fue la única solución que funcionó para mí. ¿Cómo puede adaptar este código para establecer la propiedad Placement en Top? new FrameworkPropertyMetadata("Top")no funciona.
InvalidBrainException
6
He marcado esto (después de casi 6 años, lo siento) como la respuesta correcta porque en realidad funciona en todas las versiones compatibles de Windows y lo mantiene abierto durante 49 días, que debería ser lo suficientemente largo: p
TimothyP
1
También puse esto en mi evento Window_Loaded y funciona muy bien. Lo único que tiene que hacer es asegurarse de deshacerse de cualquier "ToolTipService.ShowDuration" que haya establecido en su XAML, las duraciones que establezca en XAML anularán el comportamiento que este código intenta lograr. Gracias a John Whiter por brindar la solución.
nicko
1
FWIW, prefiero este precisamente porque es global: quiero que toda la información sobre herramientas en mi aplicación persista más tiempo sin más fanfarrias. Hacer esto también le permite aplicar selectivamente un valor más pequeño en lugares específicos del contexto, exactamente como en la otra respuesta. (Pero como siempre, esto solo es válido si usted es la aplicación; si está escribiendo una biblioteca de control u otra cosa, solo debe usar soluciones específicas del contexto; el estado global no es suyo para jugar).
Miral
1
¡Esto puede ser peligroso! Wpf usa internamente TimeSpan.FromMilliseconds () al configurar el intervalo del temporizador que hace cálculos dobles. Esto significa que cuando el valor se aplica al temporizador utilizando la propiedad Interval, puede obtener ArgumentOutOfRangeException.
jan
190

TooltipService.ShowDuration funciona, pero debe configurarlo en el objeto que tiene la información sobre herramientas, así:

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

Yo diría que se eligió este diseño porque permite la misma información sobre herramientas con diferentes tiempos de espera en diferentes controles.

Martin Konicek
fuente
4
También le permite especificar el contenido del ToolTipdirectamente, sin explícito <ToolTip>, lo que puede simplificar el enlace.
svick
15
Esta debería ser la respuesta elegida ya que es específica del contexto y no global.
Vlad
8
La duración está en milisegundos. El valor predeterminado es 5000. El código anterior especifica 12 segundos.
Contango
1
Si usa la misma instancia de información sobre herramientas con varios controles, tarde o temprano obtendrá una excepción "ya visual hijo de un padre diferente".
springy76
1
La razón principal por la que esta debería ser la respuesta correcta es que está en el espíritu de la programación real de alto nivel, directamente en el código XAML y es fácil de notar. La otra solución es un poco hacky y detallada además del punto que es global. Apuesto a que la mayoría de las personas que usaron eso se olvidaron de cómo lo hicieron en una semana.
j riv
15

Esto también me estaba volviendo loco esta noche. Creé una ToolTipsubclase para manejar el problema. Para mí, en .NET 4.0, la ToolTip.StaysOpenpropiedad no "realmente" permanece abierta.

En la siguiente clase, use la nueva propiedad ToolTipEx.IsReallyOpen, en lugar de la propiedad ToolTip.IsOpen. Obtendrá el control que desea. A través de la Debug.Print()llamada, puede ver en la ventana de salida del depurador cuántas veces this.IsOpen = falsese llama. ¿Tanto para StaysOpen, o debería decir "StaysOpen"? Disfrutar.

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

Pequeña perorata: ¿Por qué Microsoft no hizo que las DependencyPropertypropiedades (captadores / definidores) fueran virtuales para que podamos aceptar / rechazar / ajustar cambios en subclases? ¿O hacer una virtual OnXYZPropertyChangedpara todos y cada uno DependencyProperty? Ugh.

---Editar---

Mi solución anterior se ve extraña en el editor XAML: ¡la información sobre herramientas siempre se muestra, bloqueando parte del texto en Visual Studio!

Aquí hay una mejor manera de resolver este problema:

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

Algún código:

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

Conclusión: hay algo diferente en las clases ToolTipy ContextMenu. Ambos tienen clases de "servicio", como ToolTipServicey ContextMenuService, que administran ciertas propiedades, y ambos se utilizan Popupcomo un control principal "secreto" durante la visualización. Finalmente, noté que TODOS los ejemplos de información sobre herramientas XAML en la Web no usan la clase ToolTipdirectamente. En su lugar, incrustan a StackPanelcon TextBlocks. Cosas que te hacen decir: "hmmm ..."

kevinarpe
fuente
1
Debería obtener más votos en su respuesta solo por su minuciosidad. +1 de mi parte.
Hannish
ToolTipService debe colocarse en el elemento principal, consulte la respuesta de Martin Konicek anterior.
Jeson Martajaya
8

Probablemente desee usar Popup en lugar de Tooltip, ya que Tooltip asume que lo está usando en la forma predefinida de estándares de IU.

No estoy seguro de por qué StaysOpen no funciona, pero ShowDuration funciona como se documenta en MSDN: es la cantidad de tiempo que se muestra la información sobre herramientas CUANDO se muestra. Ajústelo a una pequeña cantidad (por ejemplo, 500 mseg) para ver la diferencia.

El truco en su caso es mantener el estado de "último control flotante", pero una vez que lo tenga, debería ser bastante trivial cambiar el destino de ubicación y el contenido de forma dinámica (ya sea manualmente o mediante enlace) si está utilizando una ventana emergente, u ocultando la última ventana emergente visible si está utilizando varios.

Hay algunos errores con las ventanas emergentes en cuanto al cambio de tamaño y movimiento de la ventana (las ventanas emergentes no se mueven con los contenedores), por lo que es posible que también desee tener eso en cuenta mientras modifica el comportamiento. Consulte este enlace para obtener más detalles.

HTH.

micahtan
fuente
3
También tenga en cuenta que las ventanas emergentes siempre están encima de todos los objetos del escritorio, incluso si cambia a otro programa, la ventana emergente será una parte visible y oscura del otro programa.
Jeff B
Esa es exactamente la razón por la que no me gusta usar ventanas emergentes ... porque no se encogen con el programa y se mantienen por encima de todos los demás programas. Además, cambiar el tamaño / mover la aplicación principal no mueve la ventana emergente con ella de forma predeterminada.
Rachel
FWIW, esta convención de la interfaz de usuario es una tontería de todos modos . Pocas cosas son más molestas que una información sobre herramientas que desaparece mientras la leo.
Roman Starkov
7

Si desea especificar que solo ciertos elementos en su Windowtienen una ToolTipduración efectivamente indefinida , puede definir un Styleen su Window.Resourcespara esos elementos. Aquí hay un Stylepara Buttonque tiene tal 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.">
    ...

También se puede agregar Style.Resourcespara Stylecambiar la apariencia de lo ToolTipque muestra, por ejemplo:

<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: cuando hice esto también usé BasedOn en el Stylemodo ToolTipque se aplicaría todo lo demás definido para la versión de mi control personalizado con una normal .

Jonathan Allan
fuente
5

Estuve luchando con la descripción emergente de WPF el otro día. No parece posible evitar que aparezca y desaparezca por sí solo, así que al final recurrí a manejar el Openedevento. Por ejemplo, quería evitar que se abriera a menos que tuviera algún contenido, así que manejé el Openedevento y luego hice esto:

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

Es un truco, pero funcionó.

Presumiblemente, podría manejar el Closedevento de manera similar y decirle que se abra nuevamente, manteniéndolo visible.

Daniel Earwicker
fuente
ToolTip tiene una propiedad llamada HasContent que puede usar en su lugar
benPearce
2

Solo en aras de la integridad: en el código se ve así:

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

Además, si alguna vez desea poner cualquier otro control en su información sobre herramientas, no podrá enfocarse ya que una información sobre herramientas en sí misma puede obtener el foco. Entonces, como dijo micahtan, tu mejor oportunidad es una ventana emergente.

Carlo
fuente
0

Resolví mi problema con el mismo código.

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

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

Me está funcionando. Copie esta línea en el constructor de su clase.

Vibhuti Sharma
fuente
3
esto es copiar y pegar la respuesta aceptada con la segunda mayoría de votos positivos
CodingYourLife