Memaksa tooltip WPF tetap berada di layar

119

Saya memiliki tooltip untuk Label dan saya ingin tetap terbuka sampai pengguna menggerakkan mouse ke kontrol yang berbeda.

Saya telah mencoba properti berikut di tooltip:

StaysOpen="True"

dan

ToolTipService.ShowDuration = "60000"

Namun dalam kedua kasus, keterangan alat hanya ditampilkan tepat selama 5 detik.

Mengapa nilai-nilai ini diabaikan?

TimothyP
sumber
Ada nilai maksimum yang diberlakukan di suatu tempat untuk ShowDurationproperti, anggaplah itu seperti itu 30,000. Apa pun yang lebih besar dari itu dan akan default kembali ke 5000.
Dennis
2
@ Dennis: Saya menguji ini dengan WPF 3.5 dan ToolTipService.ShowDuration="60000"berhasil. Itu tidak default untuk kembali ke 5000.
M. Dudley
@ emddudley: Apakah ToolTip tetap terbuka selama 60000ms? Anda dapat menyetel ToolTipService.ShowDurationproperti ke nilai apa pun > = 0 (ke Int32.MaxValue) namun tooltip tidak akan tetap terbuka selama itu.
Dennis
2
@ Dennis: Ya, itu tetap terbuka selama 60 detik. Ini ada di Windows 7.
M. Dudley
@ emddudley: Itu mungkin perbedaannya. Ini adalah pengetahuan dari saat saya mengembangkan Windows XP.
Dennis

Jawaban:

113

Letakkan saja kode ini di bagian inisialisasi.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
John Whiter
sumber
Ini adalah satu-satunya solusi yang berhasil untuk saya. Bagaimana Anda dapat mengadaptasi kode ini untuk menyetel properti Penempatan ke Atas? new FrameworkPropertyMetadata("Top")tidak bekerja.
InvalidBrainException
6
Saya telah menandai ini (setelah hampir 6 tahun, maaf) sebagai jawaban yang benar karena ini benar-benar berfungsi pada semua versi Windows yang didukung dan tetap terbuka selama 49 hari, yang seharusnya cukup lama: p
TimothyP
1
Saya juga meletakkan ini di acara Window_Loaded saya dan itu bekerja dengan baik. Satu-satunya hal yang harus Anda lakukan adalah memastikan bahwa Anda menyingkirkan "ToolTipService.ShowDuration" yang telah Anda tetapkan di XAML Anda, durasi yang Anda tetapkan di XAML akan menimpa perilaku yang coba dicapai kode ini. Terima kasih kepada John Whiter untuk memberikan solusinya.
nicko
1
FWIW, saya lebih suka yang ini karena ini global - saya ingin semua tooltips di aplikasi saya bertahan lebih lama tanpa gembar-gembor lebih lanjut. Melakukan ini tetap memungkinkan Anda untuk secara selektif menerapkan nilai yang lebih kecil di tempat khusus konteks, juga, persis seperti di jawaban lainnya. (Tapi seperti biasa, ini hanya berlaku jika Anda adalah aplikasinya - jika Anda menulis pustaka kontrol atau sesuatu yang lain maka Anda hanya boleh menggunakan solusi khusus konteks; status global bukan milik Anda untuk dimainkan.)
Miral
1
Ini bisa berbahaya! Wpf secara internal menggunakan TimeSpan.FromMilliseconds () saat menyetel interval pengatur waktu yang melakukan penghitungan ganda. Ini berarti bahwa ketika nilai diterapkan ke pengatur waktu menggunakan properti Interval, Anda bisa mendapatkan ArgumentOutOfRangeException.
jan
191

TooltipService.ShowDuration berfungsi, tetapi Anda harus mengaturnya pada objek yang memiliki Tooltip, seperti ini:

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

Saya akan mengatakan bahwa desain ini dipilih karena memungkinkan tooltip yang sama dengan waktu tunggu yang berbeda pada kontrol yang berbeda.

Martin Konicek
sumber
4
Ini juga memungkinkan Anda untuk menentukan konten ToolTipsecara langsung, tanpa eksplisit <ToolTip>, yang dapat membuat pengikatan lebih sederhana.
svick
15
Ini harus menjadi jawaban yang dipilih karena konteksnya spesifik dan bukan global.
Vlad
8
Durasinya dalam milidetik. Standarnya adalah 5000. Kode di atas menentukan 12 detik.
Contango
1
Jika Anda menggunakan contoh tooltip yang sama dengan beberapa kontrol, Anda cepat atau lambat akan mendapatkan pengecualian "anak visual dari induk yang berbeda".
springy76
1
Alasan utama mengapa ini harus menjadi jawaban yang benar adalah karena itu dalam semangat pemrograman tingkat tinggi yang sebenarnya, langsung ke dalam kode XAML, dan mudah diperhatikan. Solusi lainnya agak hacky dan verbose selain itu juga global. Saya yakin kebanyakan orang yang menggunakan itu lupa tentang bagaimana mereka melakukannya dalam seminggu.
j riv
15

Ini juga membuatku gila malam ini. Saya membuat ToolTipsubclass untuk menangani masalah tersebut. Bagi saya, di .NET 4.0,ToolTip.StaysOpen properti tidak "benar-benar" tetap buka.

Di kelas di bawah ini, gunakan properti baru ToolTipEx.IsReallyOpen, bukan properti ToolTip.IsOpen. Anda akan mendapatkan kendali yang Anda inginkan. Melalui Debug.Print()panggilan, Anda dapat melihat di jendela Output debugger berapa kali this.IsOpen = falsedipanggil! Begitu banyak StaysOpen, atau haruskah saya katakan "StaysOpen"? Nikmati.

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

Kata-kata kasar kecil: Mengapa Microsoft tidak membuat DependencyPropertyproperti (getter / setter) virtual sehingga kami dapat menerima / menolak / menyesuaikan perubahan dalam subclass? Atau buat virtual OnXYZPropertyChangeduntuk setiap DependencyProperty? Ugh.

--- Edit ---

Solusi saya di atas terlihat aneh di editor XAML - tooltip selalu ditampilkan, memblokir beberapa teks di Visual Studio!

Berikut cara yang lebih baik untuk mengatasi masalah ini:

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

Beberapa kode:

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

Kesimpulan: Ada sesuatu yang berbeda tentang kelas ToolTipdan ContextMenu. Keduanya memiliki kelas "layanan", seperti ToolTipServicedan ContextMenuService, yang mengelola properti tertentu, dan keduanya digunakan Popupsebagai kontrol induk "rahasia" selama tampilan. Akhirnya, saya perhatikan SEMUA contoh XAML ToolTip di Web tidak menggunakan class ToolTipsecara langsung. Sebaliknya, mereka menyematkan a StackPaneldengan TextBlocks. Hal yang membuatmu berkata: "hmmm ..."

kevinarpe
sumber
1
Anda harus mendapatkan lebih banyak suara untuk jawaban Anda hanya karena ketelitiannya. +1 dari saya.
Hannish
ToolTipService perlu ditempatkan pada elemen induk, lihat jawaban Martin Konicek di atas.
Jeson Martajaya
8

Anda mungkin ingin menggunakan Popup daripada Tooltip, karena Tooltip mengasumsikan bahwa Anda menggunakannya dengan cara standar UI yang telah ditentukan sebelumnya.

Saya tidak yakin mengapa StaysOpen tidak berfungsi, tetapi ShowDuration berfungsi seperti yang didokumentasikan di MSDN - ini adalah jumlah waktu Tooltip ditampilkan SAAT ditampilkan. Setel ke jumlah kecil (mis. 500 mdet) untuk melihat perbedaannya.

Trik dalam kasus Anda adalah mempertahankan status "kontrol yang diarahkan terakhir", tetapi setelah Anda memilikinya, mengubah target penempatan dan konten secara dinamis (baik secara manual, atau melalui penjilidan) jika Anda menggunakan salah satu Pop-up, atau menyembunyikan Pop-up terakhir yang terlihat jika Anda menggunakan beberapa.

Ada beberapa masalah dengan Popup sejauh mengubah ukuran dan memindahkan Jendela (Popup tidak bergerak dengan kontainer), jadi Anda mungkin ingin juga mengingatnya saat Anda menyesuaikan perilakunya. Lihat tautan ini untuk lebih jelasnya.

HTH.

micahtan
sumber
3
Juga berhati-hatilah karena Popup selalu berada di atas semua objek desktop - bahkan jika Anda beralih ke program lain, popup akan terlihat dan mengaburkan bagian dari program lain.
Jeff B
Itulah mengapa saya tidak suka menggunakan popup .... karena mereka tidak menyusut dengan program dan mereka tetap di atas semua program lainnya. Selain itu, mengubah ukuran / memindahkan aplikasi utama tidak memindahkan popup dengannya secara default.
Rachel
FWIW, konvensi UI ini omong kosong . Beberapa hal lebih mengganggu daripada tooltip yang menghilang saat saya membacanya.
Roman Starkov
7

Jika Anda ingin menentukan bahwa hanya elemen tertentu dalam Anda yang Windowmemiliki ToolTipdurasi efektif tak terbatas, Anda dapat menentukan a Styledi Anda Window.Resourcesuntuk elemen tersebut. Berikut ini adalah Styleuntuk Buttonyang memiliki seperti 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.">
    ...

Anda juga dapat menambahkan Style.Resourcesuntuk Stylemengubah tampilan yang ToolTipditampilkannya, misalnya:

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

Catatan: Ketika saya lakukan saya ini juga digunakan BasedOndalam Stylesehingga segala sesuatu yang lain yang ditetapkan untuk versi kontrol kustom saya dengan normal ToolTipakan diterapkan.

Jonathan Allan
sumber
5

Saya bergulat dengan WPF Tooltip hanya beberapa hari yang lalu. Sepertinya tidak mungkin untuk menghentikannya agar tidak muncul dan menghilang dengan sendirinya, jadi pada akhirnya saya terpaksa menangani Openedacara tersebut. Misalnya, saya ingin menghentikannya dari pembukaan kecuali ada beberapa konten, jadi saya menangani Openedacara tersebut dan kemudian melakukan ini:

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

Ini hack, tapi berhasil.

Agaknya Anda juga dapat menangani Closedacara tersebut dan menyuruhnya untuk dibuka kembali, sehingga membuatnya tetap terlihat.

Daniel Earwicker
sumber
ToolTip memiliki properti yang disebut HasContent yang dapat Anda gunakan sebagai gantinya
benPearce
2

Hanya demi kelengkapan: Dalam kode terlihat seperti ini:

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

Juga jika Anda ingin meletakkan kontrol lain di ToolTip Anda, itu tidak akan bisa difokuskan karena ToolTip itu sendiri bisa mendapatkan fokus. Seperti yang dikatakan micahtan, bidikan terbaik Anda adalah Popup.

Carlo
sumber
0

Memperbaiki masalah saya dengan kode yang sama.

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

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

Ini berhasil untuk saya. Salin baris ini ke konstruktor kelas Anda.

Vibhuti Sharma
sumber
3
ini adalah salin & tempel jawaban yang diterima dengan
suara positif