Do WPF have Touch-and-Hold gesture?

2020-03-27 18:42发布

问题:

Do WPF have Touch-and-Hold gesture? I cannot find event for that, so I tried to implement one for myself. I know that there is Stylus class but in WPF it does not help me. If there aren't one there is my code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace WebControlTouch
{
    /// <summary>
    /// Due to lack of Touch-and-Hold gesture, here is implementation of it. Stupid M$.
    /// </summary>
    public static class Touch_and_Hold
    {
        #region Constructor + methods
        /// <summary>
        /// Static constructor which creates timer object with 1000ms interval, also sets parameters of Timer.
        /// </summary>
        static Touch_and_Hold()
        {
            gestureTimer = new Timer(1000);
            gestureTimer.AutoReset = false;
            gestureTimer.Elapsed += gestureTimer_Elapsed;
        }
        /// <summary>
        /// On elasped (time ofc)
        /// </summary>
        /// <seealso cref="gestureTimer"/>        
        static void gestureTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            occured = true;
        }
        /// <summary>
        /// Call it on OnTouchDown event.
        /// It will start timer and will count time of touch
        /// </summary>
        /// <returns>Returns that gesture occured</returns>
        public static void onTouch()
        {
            gestureTimer.Start();
        }

        /// <summary>
        /// Call it on touch up mainwindow event (or somewhere else)
        /// It stops gesture timer
        /// </summary>
        public static void onTouchUp()
        {
        occured = false;
        }
        #endregion 
        #region Members + properties
        /// <summary>
        /// Timer for measuring touchTime
        /// </summary>
        private static Timer gestureTimer;
        /// <summary>
        /// Do tap-and-hold occured
        /// </summary>
        private static bool occured = false;
        /// <summary>
        /// Property for getting occured flag
        /// </summary>
        public static bool occuredGesture
        {
            get { return occured; }
        }
        #endregion
    }

}

If yes, please tell me name of the event. If not - try to steer me to solution. Any help will be very appreciated.

回答1:

I've previously achieved this by create a custom control that extends button to delay the trigger of a button command after a delay on press-and-hold.

public class DelayedActionCommandButton : Button

First dependency properties:

public static readonly DependencyProperty DelayElapsedProperty =
        DependencyProperty.Register("DelayElapsed", typeof(double), typeof(DelayedActionCommandButton), new PropertyMetadata(0d));

public static readonly DependencyProperty DelayMillisecondsProperty =
        DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(DelayedActionCommandButton), new PropertyMetadata(1000));

public double DelayElapsed
    {
        get { return (double)this.GetValue(DelayElapsedProperty); }
        set { this.SetValue(DelayElapsedProperty, value); }
    }

    public int DelayMilliseconds
    {
        get { return (int)this.GetValue(DelayMillisecondsProperty); }
        set { this.SetValue(DelayMillisecondsProperty, value); }
    }

These give us a control on how the delay should be and an output of how long is left.

Next I create an animation, to control the elapsed amount which when complete fires the command. There is also a cancel delay method:

    private void BeginDelay()
    {
        this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
        this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
        this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
        this._animation.Completed += (o, e) =>
        {
            this.DelayElapsed = 0d;
            this.Command.Execute(this.CommandParameter);    // Replace with whatever action you want to perform
        };

        this.BeginAnimation(DelayElapsedProperty, this._animation);
    }

    private void CancelDelay()
    {
        // Cancel animation
        this.BeginAnimation(DelayElapsedProperty, null);
    }

Finally, we wire up the event handlers:

private void DelayedActionCommandButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
    {
        this.BeginDelay();
    }

    private void DelayedActionCommandButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
    {
        this.CancelDelay();
    }

When used in XAML, you can optionally create a template that can animate based on the value of DelayElapsed to provide a countdown, or visual cue such as an expanding border, whatever takes your fancy.



回答2:

It is possible to do that in an awaitable fashion. Create a timer with specific interval. Start it when user tapped and return the method when timer elapsed. If user release the hand, return the method with false flag.

public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
    DispatcherTimer timer = new DispatcherTimer();
    TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
    timer.Interval = duration;

    MouseButtonEventHandler touchUpHandler = delegate
    {
        timer.Stop();
        if (task.Task.Status == TaskStatus.Running)
        {
            task.SetResult(false);
        }
    };

    element.PreviewMouseUp += touchUpHandler;

    timer.Tick += delegate
    {
        element.PreviewMouseUp -= touchUpHandler;
        timer.Stop();
        task.SetResult(true);
    };

    timer.Start();
    return task.Task;
}

For more information, read this post.