I am trying to create a touch and hold event handler with a variable delay in a WPF application by calling a bool task which runs a timer. If the timer elapses, the task returns true. If another event such as touch leave or touch up occurs, the task immediately returns false. Below is my event handler code:
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
// Set handled to true to avoid clicks
e.Handled = true;
var isTouchHold = await TouchHold((FrameworkElement)sender, variableTimespan);
if (isTouchHold)
TouchHoldCmd?.Execute(someParam);
else
{
// Here is where I would like to re initiate bubbling up of the event.
// This doesn't work:
e.Handled = false;
}
}
The reason I want it to propagate the event is because, for example, if the user wants to pan the scrollviewer that the element is part of and the panning gesture is started by touching my element, my touchhold works as intended in that the touch and hold command won't get triggered but neither will the scrollviewer start panning.
I tried raising the event manually but this also doesn't seem to work:
bool firedBySelf;
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
if(firedBySelf)
{
firedBySelf = false;
return;
}
...
else
{
firedBySelf = true;
e.Handled = false;
((FrameworkElement)sender).RaiseEvent(e);
}
}
How can I achieve my goal?
Edit: Here is the class containing the task:
public static class TouchHoldHelper
{
private static DispatcherTimer _timer;
private static TaskCompletionSource<bool> _task;
private static FrameworkElement _element;
private static void MouseUpCancel(object sender, MouseButtonEventArgs e) => CancelHold();
private static void MouseLeaveCancel(object sender, System.Windows.Input.MouseEventArgs e) => CancelHold();
private static void TouchCancel(object sender, TouchEventArgs e) => CancelHold();
private static void AddCancellingHandlers()
{
if (_element == null) return;
_element.PreviewMouseUp += MouseUpCancel;
_element.MouseUp += MouseUpCancel;
_element.MouseLeave += MouseLeaveCancel;
_element.PreviewTouchUp += TouchCancel;
_element.TouchUp += TouchCancel;
_element.TouchLeave += TouchCancel;
}
private static void RemoveCancellingHandlers()
{
if (_element == null) return;
_element.PreviewMouseUp -= MouseUpCancel;
_element.MouseUp -= MouseUpCancel;
_element.MouseLeave -= MouseLeaveCancel;
_element.PreviewTouchUp -= TouchCancel;
_element.TouchUp -= TouchCancel;
_element.TouchLeave -= TouchCancel;
}
private static void CancelHold()
{
if (_timer != null)
{
_timer.Stop();
_timer.Tick -= _timer_Tick;
_timer = null;
}
if (_task?.Task.Status != TaskStatus.RanToCompletion)
_task?.TrySetResult(false);
RemoveCancellingHandlers();
}
private static void _timer_Tick(object sender, EventArgs e)
{
var timer = sender as DispatcherTimer;
timer.Stop();
timer.Tick -= _timer_Tick;
timer = null;
_task.TrySetResult(true);
RemoveCancellingHandlers();
}
public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
_element = element;
_timer = new DispatcherTimer();
_timer.Interval = duration;
_timer.Tick += _timer_Tick;
_task = new TaskCompletionSource<bool>();
AddCancellingHandlers();
_timer.Start();
return _task.Task;
}
}
Edit: to better explain my intended behavior, consider how icons on a smartphone's screen work. If I tap the icon, it starts the app the icon represents. If I touch and move on an icon, it pans the screen. If I touch and hold the icon, it allows me to move the icon so I can place it somewhere else without panning the screen. If I touch and hold the icon but I don't hold it long enough to trigger the moving of the icon, it acts as if I tapped it, starting the app. I am trying to replicate these last 2 behaviors.
I am not saying my current implementation is the right approach but it's what I was able to come up with. If there is any alternative approach, I would be glad to explore it.