当WPF控件发生双击时,防止单击事件的发生。

6

我有一个处理了PreviewMouseLeftButtonDown事件的Image控件。

逻辑是当单击发生时更改图像内容,并在双击发生时激活其他视觉样式。

我知道ClickCount属性,就像一些答案所说的那样(例如这个),并成功区分了单击/双击,但问题是单击总是发生,不管下一刻是否跟随双击(无论如何,这是公平的)。 因此,在双击时,会处理两个操作-对于单击和下一次双击。

问题是:除了使用某种计时器魔法处理此情况之外,是否有任何方法可以在双击后立即防止单击发生

编辑:
我找到了一个带有良好评论的问题,它使用Windows资源管理器作为比喻-在单击选定文件后,它会等待一段时间,然后开始重命名以确保没有其他点击发生。
延迟肯定是存在的,目的是解决这个问题,但这是否意味着Windows资源管理器正好使用计时器,或者它有其他选项(某些属性或事件可以等待)来保持单击以防双击发生?


欢迎来到 StackOverflow!不错的问题——虽然不是我的领域,但希望很快会有知道这方面内容的人看到它。 - jwg
@jwg 谢谢!我很惊讶在这里找不到这样的问题,但是...现在有了 :) - Sam
2个回答

7
最终,没有收到与计时器无关的解决方案建议(我也没有找到任何建议),因此这里提供一个简单的示例,说明如何在双击发生时防止单击。 Xaml:
<Window x:Class="StackOverflow.DoubleClickExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="100" Width="150"
    MouseDown="RootElement_OnMouseDown">
</Window>

后台代码:

namespace StackOverflow.DoubleClickExample
{
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;

    public partial class MainWindow : Window
    {
        [DllImport("user32.dll")]
        public static extern uint GetDoubleClickTime();

        public MainWindow()
        {
            this.InitializeComponent();
        }

        private Guid lastGuid = Guid.Empty;

        private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 1)
            {
                // Create new unique id and save it into field.
                var guid = Guid.NewGuid();
                this.lastGuid = guid;

                // Run task asynchronously for ensuring that there is no another click
                // happened in time interval when double-click can occure.
                Task.Run(async () =>
                {
                    // Wait system double-click time interval.
                    await Task.Delay((int)GetDoubleClickTime());

                    // If no double-click occured in awaited time interval, then
                    // last saved id (saved when first click occured) will be unchanged.
                    if (guid == this.lastGuid)
                    {
                        // Here is any logic for single-click handling.
                        Trace.WriteLine("Single-click occured");
                    }
                });

                return;
            }

            // Can be here only when e.ClickCount > 1, so must change last saved unique id.
            // After that, asynchronously running task (for single-click) will detect
            // that id was changed and so will NOT run single-click logic.
            this.lastGuid = Guid.NewGuid();

            // Here is any logic for double-click handling.
            Trace.WriteLine("Double-click occured");
        }
    }
}

为了测试,请在窗口区域内单击,并跟踪编写到Visual Studio输出窗口中的消息(菜单视图 -> 输出)。

另一种方法是使用CancellationTokenSource,并在发生双击时触发其Cancel方法。只需替换lastGuid字段和RootElement_OnMouseDown方法:

private CancellationTokenSource cancellationTokenSource;

private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount == 1)
        {
            try
            {
                this.cancellationTokenSource = new CancellationTokenSource();
                var token = this.cancellationTokenSource.Token;

                // Run task asynchronously for ensuring that there is no another click
                // happened in time interval when double-click can occure.
                Task.Run(async () =>
                {
                    // Wait system double-click time interval.
                    await Task.Delay((int)GetDoubleClickTime(), token);

                    // Here is any logic for single-click handling.
                    Trace.WriteLine("Single-click occured");
                }, token);
            }
            catch (OperationCanceledException)
            {
                // This exception always occure when task is cancelled.
                // It happening by design, just ignore it.
            }

            return;
        }

        // Cancel single-click task.
        if (this.cancellationTokenSource != null)
        {
            this.cancellationTokenSource.Cancel();
        }

        // Here is any logic for double-click handling.
        Trace.WriteLine("Double-click occured");
    }

4

我猜你需要使用一个计时器。为了获取双击仍然有效的最长时间,你可以使用以下函数(已测试,输出为 500 毫秒):

[DllImport("user32.dll")]
static extern uint GetDoubleClickTime();

(来源: 如何在WPF中获取双击时间)

通常情况下,当您有多个值要绑定到一个WPF控件时,可以使用类似于ItemsSource并将其绑定到视图模型中的List。但我猜这对于Image控件不起作用。因此,您应该使用计时器,并使用上面的函数值作为计时器的值。


谢谢提供的信息,我在研究这个问题时经验性地获得了双击时间值 :) 如果计时器方案是解决方案之一,那么我可能会使用PInvoke,但我仍然希望有其他优雅的解决方案,比如操作某些属性或方法...而且我需要为我的任务使用一个确切的单个Image元素,而不是带有图像的列表(这是其他视觉样式)。 - Sam
是的,我知道这更像是一个解决方法,但我不知道有没有更优雅的解决方案。希望你能找到一个更好的WPF架构师 :D - Marco Tröster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接