Buộc một công cụ WPF ở lại trên màn hình


119

Tôi có một chú giải công cụ cho một Nhãn và tôi muốn nó mở cho đến khi người dùng di chuyển chuột đến một điều khiển khác.

Tôi đã thử các thuộc tính sau trên tooltip:

StaysOpen="True"

ToolTipService.ShowDuration = "60000"

Nhưng trong cả hai trường hợp, tooltip chỉ được hiển thị trong đúng 5 giây.

Tại sao những giá trị này bị bỏ qua?


Có một giá trị tối đa được thi hành ở đâu đó cho ShowDurationtài sản, nghĩ rằng nó là một cái gì đó như thế 30,000. Bất cứ điều gì lớn hơn thế và nó sẽ mặc định trở lại 5000.
Dennis

2
@Dennis: Tôi đã thử nghiệm điều này với WPF 3.5 và ToolTipService.ShowDuration="60000"đã hoạt động. Nó không mặc định quay lại 5000.
M. Dudley

@emddudley: ToolTip có thực sự mở trong 60000ms không? Bạn có thể đặt thuộc ToolTipService.ShowDurationtính thành bất kỳ giá trị nào > = 0 (thành Int32.MaxValue) tuy nhiên chú giải công cụ sẽ không mở trong thời gian đó.
Dennis

2
@Dennis: Vâng, nó vẫn mở trong đúng 60 giây. Đây là trên Windows 7.
M. Dudley

@emddudley: Đó có thể là sự khác biệt. Đây là kiến ​​thức từ khi tôi phát triển so với Windows XP.
Dennis

Câu trả lời:


113

Chỉ cần đặt mã này trong phần khởi tạo.

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

Đây là giải pháp duy nhất hiệu quả với tôi. Làm cách nào bạn có thể điều chỉnh mã này để đặt thuộc tính Vị trí thành Đầu trang? new FrameworkPropertyMetadata("Top")không hoạt động.
UnlimitedBrainException

6
Tôi đã đánh dấu này (sau gần 6 năm năm, xin lỗi) là câu trả lời chính xác vì đây thực sự hoạt động trên tất cả các phiên bản hỗ trợ của Windows và giữ nó mở cho 49 ngày, mà nên là đủ dài: p
TimothyP

1
Tôi cũng đặt nó trong sự kiện Window_Loaded của tôi và nó hoạt động rất tốt. Điều duy nhất bạn phải làm là đảm bảo rằng bạn thoát khỏi mọi "ToolTipService.ShowDuration" mà bạn đã đặt trong XAML, thời lượng bạn đặt trong XAML sẽ ghi đè hành vi mà mã này đang cố gắng đạt được. Cảm ơn John Whiter đã cung cấp giải pháp.
nicko

1
FWIW, tôi thích cái này chính xác vì nó mang tính toàn cầu - Tôi muốn tất cả các chú giải công cụ trong ứng dụng của mình tồn tại lâu hơn mà không cần phô trương thêm. Làm điều này vẫn cho phép bạn áp dụng có chọn lọc một giá trị nhỏ hơn ở những nơi cụ thể theo ngữ cảnh, chính xác như trong câu trả lời khác. (Nhưng như mọi khi, điều này chỉ hợp lệ nếu bạn là ứng dụng - nếu bạn đang viết thư viện điều khiển hoặc thứ gì khác thì bạn chỉ phải sử dụng các giải pháp cụ thể theo ngữ cảnh; trạng thái toàn cầu không phải là của bạn để chơi.)
Miral

1
Điều này có thể nguy hiểm! Wpf bên trong sử dụng TimeSpan.FromMilliseconds () khi đặt khoảng thời gian hẹn giờ để tính toán gấp đôi. Điều này có nghĩa là khi giá trị được áp dụng cho bộ định thời bằng thuộc tính Interval, bạn có thể nhận được ArgumentOutOfRangeException.
Tháng Một

190

TooltipService.ShowDuration hoạt động, nhưng bạn phải đặt nó trên đối tượng có Tooltip, như thế này:

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

Tôi muốn nói rằng thiết kế này đã được chọn vì nó cho phép cùng một tooltip với thời gian chờ khác nhau trên các điều khiển khác nhau.


4
Nó cũng cho phép bạn chỉ định nội dung ToolTiptrực tiếp, không rõ ràng <ToolTip>, có thể làm cho ràng buộc đơn giản hơn.
Svick

15
Đây phải là câu trả lời được lựa chọn vì nó là bối cảnh cụ thể và không toàn cầu.
Vlad

8
Thời lượng tính bằng mili giây. Mặc định là 5000. Mã ở trên chỉ định 12 giây.
Contango

1
Nếu bạn sử dụng cùng một đối tượng chú giải công cụ với nhiều điều khiển, sớm hay muộn bạn sẽ nhận được một ngoại lệ "đã là con của một phụ huynh khác".
springy76

1
Lý do chính đây phải là câu trả lời chính xác là vì nó dựa trên tinh thần lập trình cấp cao thực sự, ngay trong mã XAML và dễ nhận thấy. Giải pháp khác là hacky và verbose bên cạnh điểm toàn cầu. Tôi cá là hầu hết những người đã sử dụng mà quên mất cách họ đã làm điều đó trong một tuần.
j đối thủ

15

Điều này cũng khiến tôi phát điên tối nay. Tôi đã tạo một ToolTiplớp con để xử lý vấn đề. Đối với tôi, trên .NET 4.0, ToolTip.StaysOpentài sản không "thực sự" vẫn mở.

Trong lớp dưới đây, sử dụng tài sản mới ToolTipEx.IsReallyOpen, thay vì tài sản ToolTip.IsOpen. Bạn sẽ có được sự kiểm soát mà bạn muốn. Thông qua Debug.Print()cuộc gọi, bạn có thể xem trong cửa sổ Trình gỡ lỗi chỉ số lần this.IsOpen = falseđược gọi! Quá nhiều cho StaysOpen, hoặc tôi nên nói "StaysOpen"? Thưởng thức.

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

Rant nhỏ: Tại sao Microsoft không biến DependencyPropertycác thuộc tính (getters / setters) thành ảo để chúng ta có thể chấp nhận / từ chối / điều chỉnh các thay đổi trong các lớp con? Hoặc làm virtual OnXYZPropertyChangedcho mỗi và mọi DependencyProperty? Ừ

---Biên tập---

Giải pháp của tôi ở trên có vẻ kỳ lạ trong trình soạn thảo XAML - tooltip luôn hiển thị, chặn một số văn bản trong Visual Studio!

Đây là một cách tốt hơn để giải quyết vấn đề này:

Một số 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>

Một số mã:

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

Kết luận: Một cái gì đó khác nhau về các lớp ToolTipContextMenu. Cả hai đều có các lớp "dịch vụ", như ToolTipServiceContextMenuService, quản lý các thuộc tính nhất định và cả hai đều sử dụng Popupnhư một điều khiển cha "bí mật" trong khi hiển thị. Cuối cùng, tôi nhận thấy TẤT CẢ các ví dụ XAML ToolTip trên Web không sử dụng lớp ToolTiptrực tiếp. Thay vào đó, họ nhúng a StackPanelvới TextBlocks. Những điều khiến bạn phải thốt lên: "hmmm ..."


1
Bạn sẽ nhận được nhiều phiếu hơn cho câu trả lời của bạn chỉ vì sự kỹ lưỡng của nó. +1 từ tôi.
Hannish

ToolTipService cần được đặt trên phần tử cha, xem câu trả lời của Martin Konicek ở trên.
Jeson Martajaya

8

Bạn có thể muốn sử dụng Popup thay vì Tooltip, vì Tooltip giả định rằng bạn đang sử dụng nó theo cách tiêu chuẩn UI được xác định trước.

Tôi không chắc tại sao StaysOpen không hoạt động, nhưng ShowDuration hoạt động như được ghi lại trong MSDN - đó là khoảng thời gian Tooltip được hiển thị KHI nó hiển thị. Đặt nó thành một lượng nhỏ (ví dụ 500 msec) để thấy sự khác biệt.

Thủ thuật trong trường hợp của bạn là duy trì trạng thái "điều khiển lơ lửng cuối cùng", nhưng một khi bạn có điều đó sẽ khá đơn giản để thay đổi mục tiêu vị trí và nội dung một cách linh hoạt (bằng tay hoặc thông qua ràng buộc) nếu bạn đang sử dụng một Popup, hoặc ẩn Popup hiển thị cuối cùng nếu bạn đang sử dụng nhiều.

Có một số vấn đề với Cửa sổ bật lên khi thay đổi kích thước và di chuyển Cửa sổ (Cửa sổ bật lên không di chuyển với các thùng chứa), vì vậy bạn có thể muốn ghi nhớ điều đó trong khi bạn điều chỉnh hành vi. Xem liên kết này để biết thêm chi tiết.

HTH.


3
Ngoài ra, hãy cẩn thận rằng Cửa sổ bật lên luôn nằm trên tất cả các đối tượng máy tính để bàn - ngay cả khi bạn chuyển sang chương trình khác, cửa sổ bật lên sẽ hiển thị và che khuất một phần của chương trình khác.
Jeff B

Đó chính xác là lý do tại sao tôi không thích sử dụng cửa sổ bật lên .... vì chúng không co lại với chương trình và chúng luôn đứng đầu trong tất cả các chương trình khác. Ngoài ra, thay đổi kích thước / di chuyển ứng dụng chính không di chuyển cửa sổ bật lên theo mặc định.
Rachel

FWIW, quy ước UI này là rác rưởi . Vài điều khó chịu hơn một chú giải công cụ biến mất trong khi tôi đọc nó.
Roman Starkov

7

Nếu bạn muốn chỉ định rằng chỉ có các yếu tố nhất định trong Windowthời ToolTiplượng của bạn có thời lượng không xác định một cách hiệu quả, bạn có thể xác định một Styletrong Window.Resourcescác yếu tố đó. Dưới đây là một Stylecho Buttonrằng có một ví dụ 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.">
    ...

Người ta cũng có thể thêm Style.Resourcesvào Styleđể thay đổi diện mạo của ToolTipnó, ví dụ:

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

Lưu ý: Khi tôi đã làm tôi điều này cũng sử dụng BasedOntrong Stylequá mọi thứ khác được xác định cho các phiên bản của điều khiển quen thuộc của tôi với một bình thường ToolTipsẽ được áp dụng.


5

Tôi đã vật lộn với WPF Tooltip chỉ một ngày khác. Dường như không thể ngăn nó xuất hiện và tự biến mất, vì vậy cuối cùng tôi đã dùng đến cách xử lý Openedsự kiện. Ví dụ: tôi muốn ngăn nó mở trừ khi nó có một số nội dung, vì vậy tôi đã xử lý Openedsự kiện và sau đó đã làm điều này:

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

Đó là một hack, nhưng nó đã làm việc.

Có lẽ bạn có thể xử lý Closedsự kiện tương tự và bảo nó mở lại, do đó giữ cho nó hiển thị.


ToolTip có một thuộc tính được gọi là HasContent mà bạn có thể sử dụng thay thế
benPearce

2

Chỉ vì mục đích hoàn chỉnh: Trong mã, nó trông như thế này:

ToolTipService.SetShowDuration(element, 60000);

0

Ngoài ra, nếu bạn muốn đặt bất kỳ điều khiển nào khác vào ToolTip của mình, nó sẽ không thể tập trung được vì chính ToolTip có thể lấy nét. Giống như micahtan đã nói, bức ảnh đẹp nhất của bạn là một Popup.


0

Đã khắc phục sự cố của tôi với cùng một mã.

ToolTipService.ShowDurationProperty.OverrideMetadata (typeof (DependencyObject), FrameworkPropertyMetadata mới (Int32.MaxValue));


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

Đó là làm việc cho tôi. Sao chép dòng này vào lớp xây dựng của bạn.


3
đây là bản sao và dán các câu trả lời được chấp nhận với hầu hết các upvotes thứ hai
CodingYourLife
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.