Reputation: 2858
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:
Upvotes: 2
Views: 5103
Reputation: 19
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
}
Upvotes: 1
Reputation: 2686
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>
Upvotes: 6
Reputation: 2204
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.
Upvotes: 3