Заставить всплывающую подсказку WPF оставаться на экране

119

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

Я пробовал следующие свойства во всплывающей подсказке:

StaysOpen="True"

и

ToolTipService.ShowDuration = "60000"

Но в обоих случаях всплывающая подсказка отображается ровно 5 секунд.

Почему игнорируются эти значения?

TimothyP
источник
Где-то для ShowDurationсвойства установлено максимальное значение , думаю, это что-то вроде 30,000. Что-нибудь большее, чем это, по умолчанию вернется к 5000.
Деннис
2
@Dennis: я тестировал это с WPF 3.5 и ToolTipService.ShowDuration="60000"работал. По умолчанию он не вернулся к 5000.
М. Дадли
@emddudley: Действительно ли всплывающая подсказка остается открытой в течение 60000 мс? Вы можете установить для ToolTipService.ShowDurationсвойства любое значение> = 0 (для Int32.MaxValue), однако всплывающая подсказка не будет оставаться открытой в течение этой длины.
Деннис
2
@Dennis: Да, он оставался открытым ровно 60 секунд. Это в Windows 7.
М. Дадли
@emddudley: Может, в этом разница. Это было знание, когда я работал над Windows XP.
Деннис

Ответы:

113

Просто поместите этот код в раздел инициализации.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
Джон Уитер
источник
Это было единственное решение, которое сработало для меня. Как вы можете адаптировать этот код, чтобы установить для свойства Placement значение Top? new FrameworkPropertyMetadata("Top")не работает.
InvalidBrainException
6
Я отметил это (через почти 6 лет, извините) как правильный ответ, потому что он действительно работает во всех поддерживаемых версиях Windows и держит его открытым в течение 49 дней, что должно быть достаточно долго: p
TimothyP
1
Я также поместил это в свое событие Window_Loaded, и он отлично работает. Единственное, что вам нужно сделать, это убедиться, что вы избавились от любого «ToolTipService.ShowDuration», который вы установили в своем XAML, продолжительность, которую вы установили в XAML, переопределит поведение, которого пытается достичь этот код. Спасибо Джону Уитеру за решение.
nicko 03
1
FWIW, я предпочитаю этот именно потому, что он глобальный - я хочу, чтобы все всплывающие подсказки в моем приложении оставались дольше без лишних фанфар. Это по-прежнему позволяет выборочно применять меньшее значение в контекстно-зависимых местах, точно так же, как и в другом ответе. (Но, как всегда, это действительно только в том случае, если вы являетесь приложением - если вы пишете контрольную библиотеку или что-то еще, вы должны использовать только контекстно-зависимые решения; глобальное состояние - это не ваше дело.)
Мирал
1
Это может быть опасно! Wpf внутренне использует TimeSpan.FromMilliseconds () при установке интервала таймера, который выполняет двойные вычисления. Это означает, что когда значение применяется к таймеру с помощью свойства Interval, вы можете получить исключение ArgumentOutOfRangeException.
Январь
191

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 05
15
Это должен быть выбранный ответ, поскольку он зависит от контекста, а не глобально.
Влад
8
Продолжительность в миллисекундах. По умолчанию 5000. В приведенном выше коде указано 12 секунд.
Contango
1
Если вы используете один и тот же экземпляр всплывающей подсказки с несколькими элементами управления, вы рано или поздно получите исключение «уже визуальный дочерний элемент другого родителя».
springy76
1
Основная причина, по которой это должен быть правильный ответ, заключается в том, что он соответствует духу реального программирования высокого уровня, прямо в коде XAML и его легко заметить. Другое решение довольно хакерское и подробное, помимо того, что оно глобально. Бьюсь об заклад, большинство людей, которые использовали это, забыли о том, как они это сделали через неделю.
j
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свойства (геттеры / сеттеры) виртуальными, чтобы мы могли принимать / отклонять / корректировать изменения в подклассах? Или сделать 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. То, что заставляет вас сказать: "хммм ..."

kevinarpe
источник
1
Ваш ответ должен получить больше голосов только из-за его полноты. +1 от меня.
Ханниш
ToolTipService необходимо разместить в родительском элементе, см. Ответ Мартина Коничека выше.
Jeson Martajaya
8

Вероятно, вы захотите использовать всплывающее окно вместо всплывающей подсказки, поскольку всплывающая подсказка предполагает, что вы используете его в соответствии с предопределенными стандартами пользовательского интерфейса.

Я не уверен, почему StaysOpen не работает, но ShowDuration работает так, как описано в MSDN - это время, в течение которого всплывающая подсказка отображается, КОГДА она отображается. Установите небольшое значение (например, 500 мс), чтобы увидеть разницу.

Хитрость в вашем случае заключается в поддержании состояния «последний зависший элемент управления», но как только вы это сделаете, будет довольно просто изменить цель размещения и контент динамически (вручную или через привязку), если вы используете одно всплывающее окно, или скрытие последнего видимого всплывающего окна, если вы используете несколько.

С всплывающими окнами есть некоторые проблемы, связанные с изменением размера и перемещением окон (всплывающие окна не перемещаются с контейнерами), поэтому вы можете также помнить об этом, когда настраиваете поведение. См. Эту ссылку для получения более подробной информации.

НТН.

micahtan
источник
3
Также имейте в виду, что всплывающие окна всегда находятся поверх всех объектов рабочего стола - даже если вы переключитесь на другую программу, всплывающее окно будет видимым и скрывает часть другой программы.
Jeff B
Именно поэтому я не люблю использовать всплывающие окна ... потому что они не сжимаются вместе с программой и остаются на вершине всех других программ. Кроме того, изменение размера / перемещение основного приложения по умолчанию не перемещает всплывающее окно вместе с ним.
Рэйчел
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
2

Просто для полноты: в коде это выглядит так:

ToolTipService.SetShowDuration(element, 60000);
Ульрих Бекерт
источник
0

Кроме того, если вы когда-нибудь захотите поместить какой-либо другой элемент управления в свою всплывающую подсказку, его нельзя будет сфокусировать, поскольку сама всплывающая подсказка может получить фокус. Итак, как сказал микахтан, ваш лучший снимок - всплывающее окно.

Карло
источник
0

Исправлена ​​моя проблема с тем же кодом.

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

Аравинд С
источник
-4
ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));

У меня это работает. Скопируйте эту строку в свой конструктор класса.

Вибхути Шарма
источник
3
это копия и вставка принятого ответа со вторым по количеству голосов
CodingYourLife