Gavin Hope
Gavin Hope

Reputation: 2193

Detecting mouse movement starting and stopping with an Observable / Reactive Extensions?

Let me start with:

  1. I'm totally new to Rx
  2. I'm out of practice with C# / Linq
  3. I'm experimenting/learning so there's no application use to this question

I've read some introductory content, including this Introduction to the Reactive Framework by Matthew Podwysocki.

So I've started out with one of his examples and written some mouse-drag/line-drawing code like this:

var leftMouseDown = Observable.FromEventPattern<MouseEventArgs>(mainCanvas, "MouseLeftButtonDown");
var leftMouseUp = Observable.FromEventPattern<MouseButtonEventArgs>(mainCanvas, "MouseLeftButtonUp");
var mouseMove = Observable.FromEventPattern<MouseEventArgs>(mainCanvas, "MouseMove");

var mouseMoves = from mm in mouseMove
                 let location = mm.EventArgs.GetPosition(mainCanvas)
                 select new { location.X, location.Y };

var mouseDiffs = mouseMoves.Skip(1).Zip(mouseMoves, (l, r) => new { X1 = l.X, Y1 = l.Y, X2 = r.X, Y2 = r.Y });

var mouseDrag = from _ in leftMouseDown
                from md in mouseDiffs.TakeUntil(leftMouseUp)
                select md;

var mouseSub = mouseDrag.Subscribe(item =>
{
     var line = new Line
     {
         Stroke = Brushes.LightSteelBlue,
         X1 = item.X1,
         X2 = item.X2,
         Y1 = item.Y1,
         Y2 = item.Y2,
         StrokeThickness = 5
     };
     mainCanvas.Children.Add(line);
 });

My question

Based on playing with that example, I'd like to try reacting to mouse movement, such that:

Left mouse button is not involved, just the mouse movement.

Is that possible?

Upvotes: 1

Views: 590

Answers (2)

Timothy Shields
Timothy Shields

Reputation: 79461

First, there is a problem here:

var mouseDiffs = mouseMoves.Skip(1)
    .Zip(mouseMoves, (l, r) => new { X1 = l.X, Y1 = l.Y, X2 = r.X, Y2 = r.Y });

When an observer subscribes to mouseDiffs, two subscriptions are made to mouseMoves. You should use Publish instead to ensure only a single subscription to mouseMoves per subscription to mouseDiffs:

var mouseDiffs = mouseMoves.Publish(obs => obs.Skip(1)
    .Zip(obs, (l, r) => new { X1 = l.X, Y1 = l.Y, X2 = r.X, Y2 = r.Y }));

Now let's define an observable of colors for your canvas. Note you don't actually need your mouseDiffs observable - mouseMove will work fine for this.

var colors = mouseMove
    .Select(_ => Observable.Concat(
        Observable.Return(true),
        Observable.Return(false).Delay(TimeSpan.FromSeconds(1))))
    .Switch()
    .StartWith(false)
    .DistinctUntilChanged()
    .Select(isActive => isActive ? Color.Green : Color.Red);

You can Subscribe to colors and set your canvas color each time it emits a Color.

Upvotes: 1

Jason Boyd
Jason Boyd

Reputation: 7029

I think the key here is when is the mouse not moving? When it has been idle for 1 second, 10 seconds, 250 milliseconds? The following should do what you want. It is an observable of type bool. It yields true when the mouse is moving and false if the mouse is idle longer than the specified idle time.

int idleTime = 1000;

var mouseMoving =
    mouseMove
    .Buffer(TimeSpan.FromMilliseconds(idleTime), 1) // Buffer the mouse move events for the duration of the idle time.
    .Select(x => x.Any())                           // Return true if the buffer is not empty, false otherwise.
    .DistinctUntilChanged();                        // Only notify when the mouse moving state changes.

I will be the first to tell you that I suspect this is not the best solution. It seems needlessly wasteful to buffer mouse move events. I tried to find a solution using Sample but I was unable to make that work.?

I am much more comfortable with the updated solution thanks to Jerry's comment.

Upvotes: 5

Related Questions