web-dev-qa-db-fra.com

Forcer une info-bulle WPF à rester à l'écran

J'ai une info-bulle pour une étiquette et je veux qu'elle reste ouverte jusqu'à ce que l'utilisateur déplace la souris sur un contrôle différent.

J'ai essayé les propriétés suivantes sur l'info-bulle:

StaysOpen="True"

et

TooltipService.ShowDuration = "60000"

Mais dans les deux cas, l'info-bulle ne s'affiche que pendant exactement 5 secondes.

Pourquoi ces valeurs sont-elles ignorées?

101
TimothyP

Il suffit de mettre ce code dans la section d'initialisation. 

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

TooltipService.ShowDuration fonctionne, mais vous devez le définir sur l'objet ayant l'info-bulle, comme ceci:

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

Je dirais que cette conception a été choisie car elle permet la même infobulle avec différents délais d'expiration sur différentes commandes.

163
Martin Konicek

Cela me rendait aussi fou ce soir. J'ai créé une sous-classe ToolTip pour traiter le problème. Pour moi, sur .NET 4.0, la propriété ToolTip.StaysOpen n'est pas "vraiment" restée ouverte. 

Dans la classe ci-dessous, utilisez la nouvelle propriété ToolTipEx.IsReallyOpen au lieu de la propriété ToolTip.IsOpen. Vous obtiendrez le contrôle que vous voulez. Via l'appel Debug.Print(), vous pouvez voir dans la fenêtre de sortie du débogueur le nombre de fois où this.IsOpen = false est appelé! Voilà pour StaysOpen, ou devrais-je dire "StaysOpen"? Prendre plaisir.

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

Petit coup de gueule: Pourquoi Microsoft n'a-t-il pas rendu les propriétés DependencyProperty (getters/setters) virtuelles afin que nous puissions accepter/rejeter/ajuster les modifications des sous-classes? Ou créer un virtual OnXYZPropertyChanged pour chaque DependencyProperty? Pouah.

---Modifier---

Ma solution ci-dessus a l'air bizarre dans l'éditeur XAML: l'info-bulle est toujours affichée et bloque du texte dans Visual Studio!

Voici un meilleur moyen de résoudre ce problème:

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

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

Conclusion: Quelque chose est différent à propos des classes ToolTip et ContextMenu. Les deux ont des classes "service", telles que ToolTipService et ContextMenuService, qui gèrent certaines propriétés, et utilisent Popup comme contrôle parent "secret" lors de l'affichage. Enfin, j'ai remarqué que les exemplesALLdu XAML ToolTip sur le Web n'utilisent pas directement la classe ToolTip. Au lieu de cela, ils incorporent une StackPanel avec TextBlocks. Les choses qui vous font dire: "hmmm ..."

14
kevinarpe

Vous voudrez probablement utiliser Popup au lieu d'Info-bulle, car celle-ci suppose que vous l'utilisez de la manière prédéfinie par les normes de l'interface utilisateur.

Je ne sais pas pourquoi StaysOpen ne fonctionne pas, mais ShowDuration fonctionne comme indiqué dans MSDN: il s'agit de la durée d'affichage de l'info-bulle LORSQU'elle est affichée. Réglez-le sur une petite quantité (par exemple 500 ms) pour voir la différence.

Dans votre cas, l’astuce consiste à conserver l’état du "dernier contrôle survolé", mais une fois que vous avez compris, il devrait être relativement simple de modifier la cible de l’emplacement et le contenu de manière dynamique (manuellement ou via la liaison) si vous utilisez un seul Popup, ou masquer le dernier Popup visible si vous utilisez plusieurs.

Il y a quelques pièges avec Popups aussi loin que le redimensionnement et le déplacement de la fenêtre (les Popups ne bougent pas avec les conteneurs), donc vous voudrez peut-être aussi en tenir compte pendant que vous modifiez le comportement. Voir ce lien pour plus de détails.

HTH.

8
micahtan

Si vous souhaitez spécifier que seuls certains éléments de votre Window ont une durée Effectivement indéfinie ToolTip, vous pouvez définir une Style dans votre Window.Resources pour ces éléments. Voici une Style pour Button qui a une telle 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.">
    ...

On peut aussi ajouter Style.Resources à la Style pour changer l’apparence de la ToolTip affichée, par exemple:

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

Remarque: lorsque j'ai fait cela, j'ai également utilisé BasedOn dans Style pour que tout le reste défini pour la version de mon contrôle personnalisé avec une ToolTip normale soit appliqué.

7
Jonathan Allan

Je luttais avec l'info-bulle WPF seulement l'autre jour. Il ne semble pas possible de l'empêcher d'apparaître et de disparaître d'elle-même. J'ai donc finalement eu recours à la gestion de l'événement Opened. Par exemple, je voulais empêcher son ouverture si elle ne contenait pas un contenu. J'ai donc géré l'événement Opened, puis j'ai:

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

C'est un hack, mais ça a fonctionné.

Vraisemblablement, vous pourriez également gérer l'événement Closed et lui dire de s'ouvrir à nouveau, en le gardant ainsi visible.

5
Daniel Earwicker

Juste pour être complet: Dans le code, cela ressemble à ceci:

ToolTipService.SetShowDuration(element, 60000);
2
Ulrich Beckert

Mon problème est résolu avec le même code.

ToolTipService.ShowDurationProperty.OverrideMetadata ( Typeof (DependencyObject), nouveau FrameworkPropertyMetadata (Int32.MaxValue));

0
Aravind S

De même, si vous souhaitez placer un autre contrôle dans votre info-bulle, il ne sera pas possible de faire la mise au point, car une info-bulle elle-même peut l'être. Donc, comme dit Micahtan, votre meilleur coup est un Popup.

0
Carlo