Змушуючи підказку WPF залишатися на екрані


119

У мене є підказка для мітки, і я хочу, щоб вона залишалася відкритою, поки користувач не перемістить мишу на інший елемент управління.

Я випробував наступні властивості на підказці:

StaysOpen="True"

і

ToolTipService.ShowDuration = "60000"

Але в обох випадках підказка відображається лише рівно 5 секунд.

Чому ці значення ігноруються?


Десь встановлено максимальне значення для ShowDurationвласності, подумайте, це щось на зразок 30,000. Все, що перевищує це, і за замовчуванням повернеться до 5000.
Денніс

2
@Dennis: Я перевірив це на WPF 3.5 і ToolTipService.ShowDuration="60000"працював. Це не за замовчуванням повернутися до 5000.
М. Дадлі

@emddudley: Чи дійсно ToolTip залишається відкритим на 60000 мс? Ви можете встановити для ToolTipService.ShowDurationвластивості будь-яке значення> = 0 (до Int32.MaxValue), однак підказка не буде відкритою для такої довжини.
Денніс

2
@Dennis: Так, він залишався відкритим рівно 60 секунд. Це на Windows 7.
М. Дадлі

@emddudley: Це може бути різниця. Це були знання, коли я розроблявся для Windows XP.
Денніс

Відповіді:


113

Просто поставте цей код у розділ ініціалізації.

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

Це було єдине рішення, яке працювало на мене. Як можна адаптувати цей код, щоб встановити властивість місця розташування вгорі? new FrameworkPropertyMetadata("Top")не працює.
InvalidBrainException

6
Я позначив це (через майже 6 років, вибачте) як правильну відповідь, оскільки це насправді працює на всіх підтримуваних версіях Windows і тримає його відкритим протягом 49 днів, що повинно бути досить довгим: p
TimothyP

1
Я також помістив це у свою подію Window_Loaded, і це чудово працює. Єдине, що вам потрібно зробити, це переконатися, що ви позбудетесь від будь-якого "ToolTipService.ShowDuration", який ви встановили у своєму XAML, тривалість, яку ви встановили в XAML, буде перевершувати поведінку, якої намагається досягти цей код. Завдяки Джону Уітеру за надання рішення.
nicko

1
FWIW, я віддаю перевагу саме цьому, тому що він глобальний - я хочу, щоб усі підказки в моєму додатку зберігалися довше, без додаткових фанфарів. Це все ще дозволяє вибірково застосовувати менші значення в конкретних місцях, а також точно так само, як і в іншій відповіді. (Як завжди, це справедливо лише в тому випадку, якщо ви користуєтеся програмою - якщо ви пишете контрольну бібліотеку чи щось інше, тоді ви повинні використовувати лише конкретні контекстні рішення; глобальна держава - не ваша гра.)
Міраль

1
Це може бути небезпечно! Wpf внутрішньо використовує TimeSpan.FromMilliseconds () при встановленні інтервалу таймера, який робить подвійні обчислення. Це означає, що коли значення застосовується до таймера за допомогою властивості Interval, ви можете отримати ArgumentOutOfRangeException.
січень

190

TooltipService.ShowDuration працює, але ви повинні встановити його на об'єкті, що має підказку, наприклад:

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

Я б сказав, що ця конструкція була обрана, оскільки вона дозволяє однакові підказки з різними таймаутами на різних елементах управління.


4
Це також дозволяє вказати вміст ToolTipбезпосередньо, без явного <ToolTip>, що може зробити прив'язку простішою.
svick

15
Це має бути обраною відповіддю, оскільки це контекстне значення, а не глобальне.
Влад

8
Тривалість - у мілісекундах. За замовчуванням - 5000. Код, зазначений вище, визначає 12 секунд.
Контанго

1
Якщо ви використовуєте один і той же екземпляр підказки з декількома елементами управління, ви рано чи пізно отримаєте виняток "вже візуальний дочірній інший батько".
springy76

1
Основна причина, на яку має бути правильна відповідь, полягає в тому, що це в дусі фактичного програмування на високому рівні, прямо в XAML-код, і це легко помітити. Іншим рішенням є начебто хакі і багатослівний, окрім точки, яка є глобальною. Б'юсь об заклад, що більшість людей, які користувалися цим, забули про те, як вони це зробили за тиждень.
j riv

15

Це також мене зводило з розуму сьогодні ввечері. Я створив ToolTipпідклас для вирішення проблеми. Для мене в .NET 4.0 ToolTip.StaysOpenвластивість не "справді" залишається відкритою.

У нижньому класі використовуйте нове властивість ToolTipEx.IsReallyOpenзамість властивості ToolTip.IsOpen. Ви отримаєте потрібний контроль. Через Debug.Print()дзвінок у вікні виводу налагодження можна спостерігати скільки разів this.IsOpen = falseвикликається! Стільки за StaysOpen, чи варто сказати "StaysOpen"? Насолоджуйтесь.

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

Невелика рента: Чому Microsoft не зробила DependencyPropertyвластивості (getters / setters) віртуальними, щоб ми могли прийняти / відхилити / відрегулювати зміни в підкласах? Або зробити virtual OnXYZPropertyChangedдля кожного і кожного DependencyProperty? Тьфу.

--- Редагувати ---

Моє рішення вище виглядає дивно в редакторі XAML - підказка завжди відображається, блокуючи якийсь текст у Visual Studio!

Ось кращий спосіб вирішити цю проблему:

Деякі 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>

Деякі коди:

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

Висновок: Щось інакше стосується занять ToolTipта ContextMenu. В обох є "сервісні" класи, як ToolTipServiceі ContextMenuService, які керують певними властивостями, і обидва використовують Popupяк "секретний" батьківський контроль під час відображення. Нарешті, я помітив ВСІ приклади XAML ToolTip в Інтернеті не використовують клас ToolTipбезпосередньо. Натомість вони вбудовують a StackPanelз TextBlocks. Те, що змушує вас сказати: "хммм ..."


1
Ви повинні отримати більше голосів за свою відповідь лише за її ґрунтовність. +1 від мене.
Ганніш

ToolTipService потрібно розмістити на батьківському елементі, див. Відповідь Мартина Конічека вище.
Jeson Martajaya

8

Напевно, ви хочете використовувати Popup замість Tooltip, оскільки Tooltip передбачає, що ви використовуєте його заздалегідь визначеним стандартом інтерфейсу користувача.

Я не впевнений, чому StaysOpen не працює, але ShowDuration працює так, як це зафіксовано в MSDN - це кількість часу, коли підсказка відображається, коли вона відображається. Установіть його на невелику кількість (наприклад, 500 мсек), щоб побачити різницю.

Трюк у вашому випадку полягає в підтримці стану "останнього завислого контролю", але коли ви переконаєтесь, що слід міняти ціль місця розташування та вміст динамічно (або вручну, або через прив'язку), якщо ви користуєтесь одним спливаючим меню, або приховування останнього видимого спливаючого вікна, якщо ви використовуєте кілька.

Існує кілька проблем із Popups, що стосується зміни розміру та переміщення вікон (спливаючі вікна не переміщуються з контейнерами), тому ви, можливо, захочете мати це на увазі під час налаштування поведінки. Дивіться це посилання для отримання більш детальної інформації.

HTH.


3
Також майте на увазі, що спливаючі вікна завжди знаходяться поверх усіх об’єктів робочого столу - навіть якщо ви перейдете на іншу програму, спливаюче вікно буде видимим і неясним частиною іншої програми.
Джефф Б

Саме тому мені не подобається використовувати спливаючі вікна .... тому що вони не стискаються з програмою і залишаються на вершині всіх інших програм. Крім того, зміна розміру / переміщення основної програми не переміщує спливаюче вікно з нею за замовчуванням.
Рейчел

FWIW, ця конвенція щодо інтерфейсу все одно є сміттям . Мало що дратує, ніж підказка, яка зникає, коли я її читаю.
Роман Старков

7

Якщо ви хочете вказати, що лише певні елементи у вашій Windowвласності є невизначеною ToolTipтривалістю, ви можете визначити їх Styleу своїх Window.Resourcesелементах. Ось Styleдля Buttonцього є такий 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.">
    ...

Можна також додати Style.Resourcesдо параметра, Styleщоб змінити зовнішній вигляд ToolTipпоказаного ним зображення, наприклад:

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

Примітка: Коли я зробив це я використовував BasedOnв Styleтак все інше , певний для версії мого призначеного для користувача елементу управління з нормальним ToolTipбуде застосовуватися.


5

Я боровся з WPF Tooltip лише днями. Здається, це не в змозі запобігти появі та зникненню саме по собі, тому врешті-решт я вдався до Openedподії. Наприклад, я хотів зупинити його відкриття, якщо він не містив деякого вмісту, тому я обробляв Openedподію, а потім робив це:

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

Це хак, але це спрацювало.

Імовірно, ви могли б аналогічно впоратися з Closedподією і сказати їй відкритись знову, таким чином зберігаючи її видимою.


У ToolTip є властивість під назвою HasContent, яку ви можете використовувати замість цього
benPearce


0

Крім того, якщо ви коли-небудь захочете поставити будь-який інший елемент керування у своєму ToolTip, він не буде фокусуватися, оскільки сам ToolTip може отримати фокус. Так, як сказав Міхетан, ваш найкращий знімок - це спливаюче вікно.


0

У мене виправлена ​​проблема з тим самим кодом.

ToolTipService.ShowDurationProperty.OverrideMetadata (typeof (DependencyObject), нові FrameworkPropertyMetadata (Int32.MaxValue));


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

Це працює для мене. Скопіюйте цей рядок у свій конструктор класу.


3
це копія та вставка прийнятої відповіді з другими найбільш актуальними повідомленнями
CodingYourLife
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.