Wymuszanie podpowiedzi WPF do pozostania na ekranie

119

Mam podpowiedź dotyczącą etykiety i chcę, aby pozostawała otwarta, dopóki użytkownik nie przeniesie myszy do innej kontrolki.

Wypróbowałem następujące właściwości w podpowiedzi:

StaysOpen="True"

i

ToolTipService.ShowDuration = "60000"

Ale w obu przypadkach podpowiedź jest wyświetlana tylko przez dokładnie 5 sekund.

Dlaczego te wartości są ignorowane?

Timothy P.
źródło
Jest to wartość maksymalna egzekwowane gdzieś na ShowDurationwłasność, że to jest coś 30,000. Cokolwiek większego niż to i domyślnie wróci 5000.
Dennis,
2
@Dennis: Przetestowałem to z WPF 3.5 i działałem ToolTipService.ShowDuration="60000". Domyślnie nie wracał do 5000.
M. Dudley
@emddudley: Czy ToolTip faktycznie pozostaje otwarty przez 60000 ms? Możesz ustawić ToolTipService.ShowDurationwłaściwość na dowolną wartość> = 0 (do Int32.MaxValue), jednak podpowiedź nie pozostanie otwarta dla tej długości.
Dennis
2
@Dennis: Tak, pozostawało otwarte przez dokładnie 60 sekund. To jest w systemie Windows 7.
M. Dudley,
@emddudley: To może być różnica. To była wiedza z czasów, gdy tworzyłem przeciwko Windows XP.
Dennis

Odpowiedzi:

113

Po prostu umieść ten kod w sekcji inicjalizacji.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
John Whiter
źródło
To było jedyne rozwiązanie, które działało dla mnie. Jak możesz dostosować ten kod, aby ustawić właściwość Placement na Top? new FrameworkPropertyMetadata("Top")nie działa.
InvalidBrainException,
6
Oznaczyłem to (po prawie 6 latach, przepraszam) jako poprawną odpowiedź, ponieważ to faktycznie działa na wszystkich obsługiwanych wersjach systemu Windows i utrzymuje go otwartego przez 49 dni, co powinno wystarczyć: p
TimothyP
1
Umieściłem to również w moim zdarzeniu Window_Loaded i działa świetnie. Jedyne, co musisz zrobić, to upewnić się, że pozbędziesz się wszystkich „ToolTipService.ShowDuration”, które ustawiłeś w XAML, czasy trwania ustawione w XAML zastąpią zachowanie, które ten kod próbuje osiągnąć. Dziękuję Johnowi Whiterowi za dostarczenie rozwiązania.
nicko
1
FWIW, wolę ten właśnie dlatego, że jest globalny - chcę, aby wszystkie podpowiedzi w mojej aplikacji były dłużej bez dalszych fanfar. W ten sposób nadal możesz wybiórczo zastosować mniejszą wartość w miejscach zależnych od kontekstu, dokładnie tak jak w drugiej odpowiedzi. (Ale jak zawsze, jest to ważne tylko wtedy, gdy jesteś aplikacją - jeśli piszesz bibliotekę kontrolną lub coś innego, musisz używać tylko rozwiązań kontekstowych; stan globalny nie należy do ciebie).
Miral
1
To może być niebezpieczne! Wpf wewnętrznie używa TimeSpan.FromMilliseconds () podczas ustawiania interwału licznika czasu, który wykonuje podwójne obliczenia. Oznacza to, że gdy wartość zostanie zastosowana do licznika czasu przy użyciu właściwości Interval, można uzyskać ArgumentOutOfRangeException.
jan
191

TooltipService.ShowDuration działa, ale musisz ustawić go na obiekcie posiadającym etykietkę, na przykład:

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

Powiedziałbym, że ten projekt został wybrany, ponieważ pozwala na takie same podpowiedzi z różnymi limitami czasu na różnych kontrolkach.

Martin Konicek
źródło
4
Pozwala również na określenie zawartości ToolTipbezpośrednio, bez jawności <ToolTip>, co może uprościć wiązanie.
svick
15
To powinna być wybrana odpowiedź, ponieważ jest ona specyficzna dla kontekstu, a nie globalna.
Vlad
8
Czas trwania jest w milisekundach. Wartość domyślna to 5000. Powyższy kod określa 12 sekund.
Contango,
1
Jeśli używasz tego samego wystąpienia podpowiedzi z wieloma kontrolkami, prędzej czy później otrzymasz wyjątek „już wizualny element podrzędny innego rodzica”.
springy76
1
Głównym powodem, dla którego to powinna być prawidłowa odpowiedź, jest to, że jest to zgodne z duchem rzeczywistego programowania wysokiego poziomu, bezpośrednio w kodzie XAML i łatwe do zauważenia. Drugie rozwiązanie jest trochę hacky i rozwlekłe, poza tym, że jest globalne. Założę się, że większość ludzi, którzy to wykorzystali, zapomniała o tym, jak to zrobili w ciągu tygodnia.
j riv
15

To też doprowadzało mnie do szału. Stworzyłem ToolTippodklasę do obsługi tego problemu. Dla mnie w .NET 4.0 ToolTip.StaysOpenobiekt nie jest „naprawdę” pozostaje otwarty.

W klasie poniżej użyj nowej właściwości ToolTipEx.IsReallyOpenzamiast property ToolTip.IsOpen. Uzyskasz kontrolę, jaką chcesz. Poprzez Debug.Print()rozmowy, można oglądać w oknie Output debuggera, jak wiele razy this.IsOpen = falsenazywa! To tyle StaysOpen, czy powinienem powiedzieć "StaysOpen"? Cieszyć się.

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

Mały rant: dlaczego firma Microsoft nie uczyniła DependencyPropertywłaściwości (pobierających / ustawiających) wirtualnych, abyśmy mogli akceptować / odrzucać / dostosowywać zmiany w podklasach? Albo zrobić virtual OnXYZPropertyChangeddla każdego DependencyProperty? Fuj.

---Edytować---

Moje rozwiązanie powyżej wygląda dziwnie w edytorze XAML - podpowiedź zawsze się wyświetla, blokując część tekstu w Visual Studio!

Oto lepszy sposób rozwiązania tego problemu:

Niektóre 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>

Jakiś kod:

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

Wniosek: Coś jest innego w przypadku klas ToolTipi ContextMenu. Obie mają klasy „usług”, takie jak ToolTipServicei ContextMenuService, które zarządzają określonymi właściwościami i obie używają Popupjako „tajnej” kontroli nadrzędnej podczas wyświetlania. Wreszcie zauważyłem, że WSZYSTKIE przykłady etykiet narzędzi XAML w Internecie nie używają klasy ToolTipbezpośrednio. Zamiast tego osadzają a StackPanelz TextBlocks. Rzeczy, które sprawiają, że mówisz: „hmmm ...”

kevinarpe
źródło
1
Powinieneś otrzymać więcej głosów na swoją odpowiedź tylko ze względu na jej dokładność. +1 ode mnie.
Hannish
ToolTipService należy umieścić w elemencie nadrzędnym, zobacz odpowiedź Martina Konicek powyżej.
Jeson Martajaya
8

Prawdopodobnie chcesz użyć Popup zamiast Tooltip, ponieważ Tooltip zakłada, że ​​używasz go zgodnie z predefiniowanymi standardami interfejsu użytkownika.

Nie jestem pewien, dlaczego StaysOpen nie działa, ale ShowDuration działa zgodnie z dokumentacją w MSDN - jest to czas, przez który podpowiedź jest wyświetlana, GDY jest wyświetlana. Ustaw małą wartość (np. 500 ms), aby zobaczyć różnicę.

Sztuczka w twoim przypadku polega na utrzymaniu stanu „ostatniej aktywowanej kontroli”, ale kiedy już to zrobisz, zmiana miejsca docelowego i zawartości dynamicznie (ręcznie lub przez powiązanie) powinna być dość trywialna, jeśli używasz jednego Popup, lub ukrywanie ostatniego widocznego Popup, jeśli używasz wielu.

Istnieje kilka problemów z wyskakującymi okienkami, jeśli chodzi o zmianę rozmiaru i przesuwanie okna (wyskakujące okienka nie poruszają się w / w kontenerach), więc możesz również mieć to na uwadze podczas dostosowywania zachowania. Zobacz ten link, aby uzyskać więcej informacji.

HTH.

micahtan
źródło
3
Uważaj również, że wyskakujące okienka zawsze znajdują się nad wszystkimi obiektami na pulpicie - nawet jeśli przełączysz się do innego programu, wyskakujące okienko będzie widoczne i zasłoni część innego programu.
Jeff B,
Właśnie dlatego nie lubię używać wyskakujących okienek ... ponieważ nie zmniejszają się wraz z programem i pozostają nad wszystkimi innymi programami. Ponadto zmiana rozmiaru / przeniesienie głównej aplikacji nie powoduje domyślnie przeniesienia wyskakującego okienka.
Rachel
FWIW, ta konwencja UI i tak to bzdura . Niewiele rzeczy jest bardziej irytujących niż podpowiedź, która znika, gdy ją czytam.
Roman Starkov
7

Jeśli chcesz określić, że tylko niektóre elementy w twoim Windowmają efektywnie nieokreślony czas ToolTiptrwania, możesz zdefiniować Stylew swoim Window.Resourcesdla tych elementów. Oto Stylefor, Buttonktóry ma taki 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.">
    ...

Można również dodać, Style.Resourcesaby Stylezmienić wygląd ToolTippokazanych na nim pokazów, na przykład:

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

Uwaga: Kiedy to zrobiłem, użyłem również BasedOnw programie, Stylewięc wszystko inne zdefiniowane dla wersji mojej kontrolki niestandardowej z normalnym ToolTipzostanie zastosowane.

Jonathan Allan
źródło
5

Zmagałem się z WPF Tooltip dopiero niedawno. Wydaje się, że nie da się powstrzymać tego przed pojawieniem się i zniknięciem, więc w końcu postanowiłem zająć się tym Openedwydarzeniem. Na przykład chciałem zatrzymać otwieranie go, chyba że ma jakąś treść, więc obsłużyłem Openedzdarzenie, a potem zrobiłem to:

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

To hack, ale zadziałał.

Prawdopodobnie mógłbyś w podobny sposób obsłużyć Closedzdarzenie i nakazać mu ponowne otwarcie, dzięki czemu jest widoczne.

Daniel Earwicker
źródło
ToolTip ma właściwość o nazwie HasContent, której można użyć zamiast tego
benPearce
2

Gwoli ścisłości: w kodzie wygląda to tak:

ToolTipService.SetShowDuration(element, 60000);
Ulrich Beckert
źródło
0

Ponadto, jeśli kiedykolwiek zechcesz umieścić jakąkolwiek inną kontrolę w swojej podpowiedzi, nie będzie można jej skupić, ponieważ sama podpowiedź może uzyskać fokus. Tak jak powiedział Micahtan, najlepszym strzałem jest Popup.

Carlo
źródło
0

Naprawiono mój problem za pomocą tego samego kodu.

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

Aravind S.
źródło
-4
ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));

To działa dla mnie. Skopiuj ten wiersz do konstruktora klasy.

Vibhuti Sharma
źródło
3
to jest kopiowanie i wklejanie zaakceptowanej odpowiedzi z drugą największą liczbą
głosów