WPF - 检测鼠标按下的持续时间

7

如何最好地检测鼠标按钮在特定元素上按下并保持一段时间?

2个回答

12

谢谢你的建议,我已经创建了一个附加属性来避免任何代码后台:

using System;
using System.Windows;
using System.Windows.Threading;

/// <summary>
/// Represents a particular mouse button being pressed
/// </summary>
public enum MouseButtonType
{
    /// <summary>
    /// Default selection
    /// </summary>
    None,

    /// <summary>
    /// Left mouse button
    /// </summary>
    Left,

    /// <summary>
    /// Right mouse button
    /// </summary>
    Right,

    /// <summary>
    /// Either mouse button
    /// </summary>
    Both
}

/// <summary>
/// Provides functionality for detecting when a mouse button has been held
/// </summary>
public class MouseDownWait
{
    /// <summary>
    /// States which mouse button press should be detected
    /// </summary>
    public static readonly DependencyProperty MouseButtonProperty =
        DependencyProperty.RegisterAttached(
            "MouseButton",
            typeof(MouseButtonType),
            typeof(MouseDownWait),
            new PropertyMetadata(
                (o, e) =>
                    {
                    var ctrl = o as UIElement;
                    if (ctrl != null)
                    {
                        new MouseDownWait(ctrl);
                    }
                }));

    /// <summary>
    /// The time (in milliseconds) to wait before detecting mouse press
    /// </summary>
    public static readonly DependencyProperty TimeProperty = DependencyProperty.RegisterAttached(
        "Time", typeof(int), typeof(MouseDownWait), new FrameworkPropertyMetadata(0, OnTimePropertyChanged));

    /// <summary>
    /// Method to be called when the mouse press is detected
    /// </summary>
    public static readonly DependencyProperty DetectMethodProperty =
        DependencyProperty.RegisterAttached(
            "DetectMethod",
            typeof(string),
            typeof(MouseDownWait));

    /// <summary>
    /// Target object for the method calls (if not the datacontext)
    /// </summary>
    public static readonly DependencyProperty MethodTargetProperty =
        DependencyProperty.RegisterAttached("MethodTarget", typeof(object), typeof(MouseDownWait));      

    /// <summary>
    /// The timer used to detect mouse button holds
    /// </summary>
    private static readonly DispatcherTimer Timer = new DispatcherTimer();

    /// <summary>
    /// The element containing the attached property
    /// </summary>
    private readonly UIElement element;

    /// <summary>
    /// Initializes a new instance of the <see cref="MouseDownWait"/> class.
    /// </summary>
    /// <param name="element">The element.</param>
    public MouseDownWait(UIElement element)
    {
        this.element = element;

        if (this.element == null)
        {
            return;
        }

        this.element.MouseLeftButtonDown += ElementMouseLeftButtonDown;
        this.element.MouseLeftButtonUp += ElementMouseLeftButtonUp;
        this.element.MouseRightButtonDown += ElementMouseRightButtonDown;
        this.element.MouseRightButtonUp += ElementMouseRightButtonUp;
        this.element.MouseDown += ElementMouseDown;
        this.element.MouseUp += ElementMouseUp;

        Timer.Tick += this.TimerTick;
    }

    /// <summary>
    /// Gets the mouse button type
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>
    /// The mouse button type
    /// </returns>
    public static MouseButtonType GetMouseButton(UIElement element)
    {
        return (MouseButtonType)element.GetValue(MouseButtonProperty);
    }

    /// <summary>
    /// Sets the mouse button type
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The type of mouse button</param>
    public static void SetMouseButton(UIElement element, MouseButtonType value)
    {
        element.SetValue(MouseButtonProperty, value);
    }

    /// <summary>
    /// Gets the time.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>The time in milliseconds</returns>
    public static int GetTime(UIElement element)
    {
        return (int)element.GetValue(TimeProperty);
    }

    /// <summary>
    /// Sets the time.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetTime(UIElement element, int value)
    {
        element.SetValue(TimeProperty, value);
    }

    /// <summary>
    /// Sets the detect method
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetDetectMethod(UIElement element, string value)
    {
        element.SetValue(DetectMethodProperty, value);
    }

    /// <summary>
    /// Gets the detect method
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>method name</returns>
    public static string GetDetectMethod(UIElement element)
    {
        return (string)element.GetValue(DetectMethodProperty);
    }

    /// <summary>
    /// Gets the method target.
    /// </summary>
    /// <param name="ctrl">The CTRL .</param>
    /// <returns>method target (i.e. viewmodel)</returns>
    public static object GetMethodTarget(UIElement ctrl)
    {
        var result = ctrl.GetValue(MethodTargetProperty);
        if (result == null)
        {
            var fe = ctrl as FrameworkElement;
            if (fe != null)
            {
                result = fe.DataContext;
            }
        }

        return result;
    }

    /// <summary>
    /// Sets the method target.
    /// </summary>
    /// <param name="ctrl">The CTRL .</param>
    /// <param name="value">The value.</param>
    public static void SetMethodTarget(UIElement ctrl, object value)
    {
        ctrl.SetValue(MethodTargetProperty, value);
    }

    /// <summary>
    /// Called when the time property changes.
    /// </summary>
    /// <param name="d">The dependency object.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void OnTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Timer.Interval = TimeSpan.FromMilliseconds((int)e.NewValue);
    }

    /// <summary>
    /// Called when a mouse down is detected
    /// </summary>
    private static void MouseDownDetected()
    {
        Timer.Start();
    }

    /// <summary>
    /// Called when a mouse up is detected
    /// </summary>
    private static void MouseUpDetected()
    {
        Timer.Stop();
    }

    /// <summary>
    /// Checks if the mouse button has been detected.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="type">The mouse button type.</param>
    /// <param name="mouseDown">if set to <c>true</c> [mouse down].</param>
    private static void CheckMouseDetected(object sender, MouseButtonType type, bool mouseDown)
    {
        var uiElement = sender as UIElement;

        if (uiElement == null)
        {
            return;
        }

        if (GetMouseButton(uiElement) != type)
        {
            return;
        }

        if (mouseDown)
        {
            MouseDownDetected();
        }
        else
        {
            MouseUpDetected();
        }
    }

    /// <summary>
    /// Called when the mouse down event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Both, true);
    }

    /// <summary>
    /// Called when the mouse up event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Both, false);
    }

    /// <summary>
    /// Called when the left mouse down event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Left, true);
    }

    /// <summary>
    /// Called when the left mouse up event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Left, false);
    }

    /// <summary>
    /// Called when the right mouse down event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Right, true);
    }

    /// <summary>
    /// Called when the right mouse up event fires
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private static void ElementMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        CheckMouseDetected(sender, MouseButtonType.Right, false);
    }

    /// <summary>
    /// Called on each timer tick
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private void TimerTick(object sender, EventArgs e)
    {
        Timer.Stop();

        var method = GetDetectMethod(this.element);

        if (!string.IsNullOrEmpty(method))
        {
            this.InvokeMethod(method);
        }
    }

    /// <summary>
    /// Invokes the method.
    /// </summary>
    /// <param name="methodName">Name of the method.</param>
    /// <param name="parameters">The parameters.</param>
    private void InvokeMethod(string methodName, params object[] parameters)
    {
        var target = GetMethodTarget(this.element);
        var targetMethod = target.GetType().GetMethod(methodName);
        if (targetMethod == null)
        {
            throw new MissingMethodException(methodName);
        }

        targetMethod.Invoke(target, parameters);
    }
}

用法:

<Border BorderBrush="Gray" BorderThickness="1" Height="200" Width="200" Background="Transparent" 
        local:MouseDownWait.MouseButton="Both"
        local:MouseDownWait.Time="1000"
        local:MouseDownWait.DetectMethod="MouseDetected">

        <TextBlock x:Name="Title" HorizontalAlignment="Stretch" VerticalAlignment="Center" TextAlignment="Center" FontSize="28"  />

</Border>

当指定时间过去后,这将调用您的数据上下文(ViewModel)上的方法。您可以检测左、右或两个鼠标按钮。


+1。好的解决方案。只需使用Timer.Stop()处理MouseLeave即可。 - Paw Baltzersen
@devdigital - 我也有一个类似的问题,我正在尝试解决一个敏感触摸屏的问题。我需要过滤掉手部靠近屏幕时引起的手指触摸,并希望尝试使用自定义按钮来检测稍微长一点的按压。看起来你上面所做的可能有效,但你能解释一下这个代码如何集成到应用程序中吗? - Duncan Groenewald
如果我在多个控件上应用附加属性,则无论单击的控件如何,都会触发不同的Detect方法,是否可能使其像常规实例一样工作,而不是静态的? - MatiasK
MatiasK,我通过添加一个名为pressed的私有非静态布尔值来解决了这个问题。在CheckMouseDetected中,在if(mouseDown)下面放置pressed = true,并在else下面放置pressed = false。然后在TimerTick中,将该行更改为if(!string.IsNullOrEmpty(method)&& pressed)。在TimerTick的末尾,添加pressed = false。 - Matt Becker

9

您需要为该对象添加MouseDownMouseUp处理程序。在MouseDown记录中加入DateTime.Now。如果在MouseUp处理程序中:

DateTime.Now.Subtract(clickTime).TotalSeconds > your_seconds_value

然后触发新事件MouseClickedForXseconds

如果您不想等待鼠标弹起事件,那么您需要在MouseDown方法上启动一个计时器,该计时器会触发您的MouseClickedForXSeconds事件。此计时器将被鼠标弹起事件取消。


1
+1。根据所需的精度,考虑使用StopWatch类而不是记录DateTime.Now可能也值得考虑。http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx - Ian Nelson

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