I'm trying to implement a UI control where the user can click a button to have a thing move by a little, or hold the button down and have the thing move while the button is held down.
Let's say I have Task<Unit> StartMove()
, Task<Unit> StopMove()
and Task<Unit> MoveStep()
. The button click should perform the MoveStep()
and the button hold should start the move and then stop the move immediately when the button is released. Rapid clicks (double clicks) should be ignored while the move is happening and there should not be more than 2x MoveStep commands sent per second. There also needs to be some fail safe which stops the move on an error or after a long timeout of let's say 5 mins.
The button press is represented by a property on the Button object, which fires a true
value when the user presses the button and a false
when it is released, this value is called IsPressed on the regular WPF button. A true value followed by a false value less than a second later represents a click and a true value followed by a false value more than a second later represents a hold (this sec value could also be tuned to half a second).
The question boils down to taking a stream of such true / false values that arrive at a random interval (think: The monkey is pressing the button randomly) and determining from this stream if the button is clicked or held down. Based on this, the actions should be triggered: MoveStep
for a click and StartMove
then StopMove
for a button hold.
Working code snippet
I finally got something together that kinda works.
So far I have MainWindow
public partial class MainWindow : Window, IViewFor<AppViewModel>
{
public AppViewModel ViewModel { get; set; }
object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as AppViewModel; }
public MainWindow()
{
ViewModel = new AppViewModel();
DataContext = ViewModel;
InitializeComponent();
this.WhenAnyValue(x => x.MoveLeftButton.IsPressed).InvokeCommand(this, x => x.ViewModel.MoveLeftCommand);
}
protected override void OnClosing(CancelEventArgs e)
{
ViewModel.Dispose();
base.OnClosing(e);
}
}
An AppViewModel
public class AppViewModel : ReactiveObject, IDisposable
{
public ReactiveCommand<bool, bool> MoveLeftCommand { get; protected set; }
public AppViewModel()
{
MoveLeftCommand = ReactiveCommand.CreateFromTask<bool, bool>(isPressed => _MoveLeft(isPressed));
MoveLeftCommand.Buffer(TimeSpan.FromMilliseconds(500))
.Do(x => _InterpretCommand(x))
.Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"))
}
private Task<bool> _MoveLeft(bool isPressed)
{
return Task.Run(() => isPressed); // Just to set a breakpoint here really
}
private static void _InterpretCommand(IList<bool> listOfBools)
{
if (listOfBools == null || listOfBools.Count == 0)
{
return;
}
if (listOfBools.First() == false)
{
Console.WriteLine("Stop move");
return;
}
if (listOfBools.Count == 1 && listOfBools.First() == true)
{
Console.WriteLine("Start move");
return;
}
if (listOfBools.Count >= 2)
{
Console.WriteLine("Click move");
return;
}
}
}
And my MainWindow.xaml is really just
<Button x:Name="MoveLeftButton" Content="Left"/>
Random sequence example
var rands = new Random();
rands.Next();
var better = Observable.Generate(
true,
_ => true,
x => !x,
x => x,
_ => TimeSpan.FromMilliseconds(rands.Next(1000)))
.Take(20);
better.Buffer(TimeSpan.FromMilliseconds(500))
.Do(x => _InterpretCommand(x))
.Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"));
static string TimeStamp => DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
This produces the output
2017-10-06 19:11:54.231
Start move
2017-10-06 19:11:54.720 True
2017-10-06 19:11:55.220
Stop move
2017-10-06 19:11:55.719 False,True
Stop move
2017-10-06 19:11:56.221 False
Start move
2017-10-06 19:11:56.719 True
Stop move
2017-10-06 19:11:57.222 False
2017-10-06 19:11:57.719
Start move
2017-10-06 19:11:58.220 True
Stop move
2017-10-06 19:11:58.720 False
2017-10-06 19:11:59.219
Click move
2017-10-06 19:11:59.719 True,False
2017-10-06 19:12:00.217
Start move
2017-10-06 19:12:00.719 True
Stop move
2017-10-06 19:12:01.221 False
Click move
2017-10-06 19:12:01.722 True,False
Start move
2017-10-06 19:12:02.217 True
2017-10-06 19:12:02.722
Stop move
2017-10-06 19:12:03.220 False
2017-10-06 19:12:03.720
Start move
2017-10-06 19:12:04.217 True
Stop move
2017-10-06 19:12:04.722 False
Start move
2017-10-06 19:12:05.220 True
Stop move
2017-10-06 19:12:05.516 False