1

I am simulating a handheld device with WPF and MVVM (viewmodel is a state-machine, view is a simulated plastic case with buttons).

The gesture that turns the device on and off is a "long click" in a button. For exemple, during use, if I press "OK" button, it displays some screen, but if I HOLD it clicked for more than three seconds, it should (in a simulated way) turn the device off.

I took a look in RepeatButton, with its Delay and Interval properties, but those seem to fire the same Click event. What I need is to fire a regular Click if I hold the button for less than three seconds, and fire another, different LongClick (possibly once) if I hold it for more than three seconds.

How can I do this, using RepeatButton or even a regular Button?

heltonbiker
  • 26,657
  • 28
  • 137
  • 252

2 Answers2

3

One way you could do this would be to bind to the MouseDown and MouseUp events. Use something like a Stopwatch that gets started on MouseDown and check the amount of time that's elapsed on MouseUp. If it's less than 3 seconds, do your Click() action. If it's more than 3 seconds, do your LongClick() action.

private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    stopwatch = new Stopwatch();
    stopwatch.Start();
}

private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    stopwatch.Stop();

    if (stopwatch.ElapsedMilliseconds >= 3000)
    {
         // do Click()
    }
    else
    {
        // do LongClick
    }
}

And here's a solution for RepeatButton:

private bool isLongClick;
private bool hasAlreadyLongClicked;
private void RepeatButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    isLongClick = false;
    hasAlreadyLongClicked = false;
    stopwatch = new Stopwatch();
    stopwatch.Start();
}

private void RepeatButton_Click(object sender, RoutedEventArgs e)
{
    if (!hasAlreadyLongClicked && stopwatch.ElapsedMilliseconds >= 3000)
    {
        hasAlreadyLongClicked = true;
        isLongClick = true;
        // do your LongClick action
    }
}

private void RepeatButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    if (!isLongClick)
    {
        // do short click action
    }
}

The trick here is that a RepeatButton is basically just a Button that fires Click at every interval. So, if we start the Stopwatch on PreviewMouseDown for the button, we can check the elapsed time on the stopwatch every time the Click event fires, and modify our action based on the result.

furkle
  • 5,019
  • 1
  • 15
  • 24
  • The LongClick event should not wait until I relase the mouse, but actualy when three seconds have elapsed. But that would be an interesting (and simple, and effective) solution. Gonna give it a try. – heltonbiker Oct 31 '14 at 20:20
  • @heltonbiker Hm. That's a lot closer to the function of the RepeatButton than the Button. I'll see if I can come up with something like that. – furkle Oct 31 '14 at 20:22
  • Yeah, my current widgets are repeatbuttons, since this is the hardware behavior they emulate. The core of my question would be "how to differentiate the first click from the subsequent ones?" – heltonbiker Oct 31 '14 at 20:23
  • @heltonbiker Got it! Check out the second solution. – furkle Oct 31 '14 at 20:30
  • Your last code makes me believe that I will ALWAYS get a variable number of Click events before start getting LongClick ones? – heltonbiker Oct 31 '14 at 20:30
  • @heltonbiker You'll get a click event every time the interval you set on the RepeatButton passes, yes. That's the way the Click event is implemented for the RepeatButton. You may want to set some boolean flags to make sure that the LongClick action is only performed once. – furkle Oct 31 '14 at 20:32
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64064/discussion-between-furkle-and-heltonbiker). – furkle Oct 31 '14 at 20:36
0

After a weekend's meditation, I devised the following, working solution:

  1. Create two boolean flags in ViewModel: ClickedShort and ClickedLong;
  2. Binding the Command property on button, via commanding, to OK method on ViewModel;
  3. The OK command is like this:

    public void OK()
    {
        if (!ClickedShort)
        {
            HandleShortClick();
            ClickedShort = true;
        }
        else if (!ClickedLong)
        {
            HandleLongClick();
            ClickedLong = true;
        }
    }    
    
  4. Have two CheckBoxes in XAML, with IsChecked properties bound to the boolean flags. These are just relays so that I can send information from View to ViewModel via binding:

        <CheckBox x:Name="ClickedShort" IsChecked="{Binding ClickedShort, Mode=OneWayToSource}" Visibility="Collapsed"/>
        <CheckBox x:Name="ClickedLong" IsChecked="{Binding ClickedLong, Mode=OneWayToSource}" Visibility="Collapsed"/>
    
  5. Have a MouseUp event in OK button to reset those flags to false when I release the button.

heltonbiker
  • 26,657
  • 28
  • 137
  • 252