简体   繁体   中英

MVVM: Button Hold event command

I want to be able to assign two different Command to a Button :

  • Click event Command
  • Hold event Command which uses HoldTimeout property to specify the hold duration

    public static readonly DependencyProperty HoldCommandProperty = DependencyProperty.Register( "HoldCommand", typeof(ICommand), typeof(CommandButton), new PropertyMetadata(null, CommandChanged)); public ICommand HoldCommand { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }

How to calculate the time for click & hold and where should the calculation be done? I am not sure if handling Click event is the right place if using the 'Command' property of an button.

The result XAML should look something like that:

<CommandButton x:Name="InputButton" 
               Command="{Binding PrimaryCommand}"
               CommandParameter="{Binding}"
               HoldCommand="{Binding SecondaryCommand}"
               HoldCommandParameters="{Binding}"
               HoldTimeout="2000"/>

I have read how to implement double-clicks but this is not exactly it:

You need to create a custom control and use DispatcherTimer class to time it. You can add another boolean and command property to activate this behaviour.

the control is as follows:

public class SmartButton : Button
{
    private DispatcherTimer _timer;


    public int MillisecondsToWait
    {
        get { return (int)GetValue(MillisecondsToWaitProperty); }
        set { SetValue(MillisecondsToWaitProperty, value); }
    }

    public DispatcherTimer Timer
    {
        get { return _timer; }
        set { _timer = value; }
    }



    public ICommand ClickAndHoldCommand
    {
        get { return (ICommand)GetValue(ClickAndHoldCommandProperty); }
        set { SetValue(ClickAndHoldCommandProperty, value); }
    }


    public bool EnableClickHold
    {
        get { return (bool)GetValue(EnableClickHoldProperty); }
        set { SetValue(EnableClickHoldProperty, value); }
    }

    // Using a DependencyProperty as the backing store for EnableClickHold.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EnableClickHoldProperty =
        DependencyProperty.Register("EnableClickHold", typeof(bool), typeof(SmartButton), new PropertyMetadata(false));



    // Using a DependencyProperty as the backing store for ClickAndHoldCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ClickAndHoldCommandProperty =
        DependencyProperty.Register("ClickAndHoldCommand", typeof(ICommand), typeof(SmartButton), new UIPropertyMetadata(null));



    // Using a DependencyProperty as the backing store for MillisecondsToWait.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MillisecondsToWaitProperty =
        DependencyProperty.Register("MillisecondsToWait", typeof(int), typeof(SmartButton), new PropertyMetadata(0));


    public SmartButton()
    {
        this.PreviewMouseLeftButtonUp += OnPreviewMouseLeftButtonUp;
        this.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;

    }

    private void OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (EnableClickHold)
        {

                bool isMouseReleaseBeforeHoldTimeout = Timer.IsEnabled;
                ResetAndRemoveTimer();
                // Consider it as a mouse click 
                if (isMouseReleaseBeforeHoldTimeout && Command != null)
                {
                    Command.Execute(CommandParameter);
                }
                e.Handled = true;
        }
    }

    private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (EnableClickHold)
        {
            Timer = new DispatcherTimer(DispatcherPriority.Normal, this.Dispatcher)
            {
                Interval = TimeSpan.FromMilliseconds(MillisecondsToWait)
            };
            Timer.Tick += Timer_Tick;
            Timer.IsEnabled = true;
            Timer.Start();
            e.Handled = true;
        }
    }

    void Timer_Tick(object sender, EventArgs e)
    {
        if(ClickAndHoldCommand != null)
        {
            this.ClickAndHoldCommand.Execute(this.CommandParameter);
        }

        ResetAndRemoveTimer();
    }

    private void ResetAndRemoveTimer()
    {
        if (Timer == null) return;
        Timer.Tick -= Timer_Tick;
        Timer.IsEnabled = false;
        Timer.Stop();
        Timer = null;
    }
}

The xaml of this should look like

 <wpfMouseClick:SmartButton x:Name="MySmartButton"
                                   Width="100"
                                   Height="50"
                                   ClickAndHoldCommand="{Binding Path=MyTestCommand,
                                                                 ElementName=MyWindow}"
                                   EnableClickHold="True"
                                   MillisecondsToWait="1000">
            Click and Hold
        </wpfMouseClick:SmartButton>

Look into the RepeatButton control, which fires a Click event repeatedly from the time you click it to the time it is released.

To expand on this, you can control the interval of Click events fired, and keep track of how many will execute in a given time. For example, if the Interval property is set to 1000 , it will fire a Click event every second. Keep track of how many are fired with a counter; once 5 have fired this means the user held the button down for five seconds and you can put your "Click & Hold" event logic in the RepeatButton Click event handler and then reset the counter.

How about using EventTriggers and a StopWatch.

<UserControl xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <Button>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="PreviewMouseDown">
                <i:InvokeCommandAction Command="{Binding DownCmd}" />
            </i:EventTrigger>
            <i:EventTrigger EventName="PreviewMouseUp">
                <i:InvokeCommandAction Command="{Binding UpCmd}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
</UserControl>

This is the C#. I am using the code in a ViewModel.

Stopwatch _buttonHoldStopWatch;
public DelegateCommand DownCmd { get; set; }
public DelegateCommand UpCmd { get; set; }

// Delegate commands are from the Prism framework but you can switch these out to 
regular ICommands
ResetValueDownCmd = new DelegateCommand(Down);
ResetValueUpCmd = new DelegateCommand(Up);

// User pressed down
private void Down(object dayObject)
{
    _buttonHoldStopWatch.Start(); // start watch
}

// User left go of press
private void Up(object dayObject)
{
    // Did the user hold down the button for 0.5 sec
    if (_buttonHoldStopWatch.ElapsedMilliseconds >= 500)
    {
        // Do something
    }

    _buttonHoldStopWatch.Stop(); // stop watch
    _buttonHoldStopWatch.Reset(); // reset elapsed time
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM