This one's got me a bit baffled.
I've written some extension methods on UIElement to provide Observables on some of the mouse events. Here are the relevant ones:
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseLeftButtonDown(this UIElement element)
{
return Observable.FromEventPattern<MouseEventArgs>(element, "MouseLeftButtonDown");
}
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseMove(this UIElement element)
{
return Observable.FromEventPattern<MouseEventArgs>(element, "MouseMove");
}
So far, so mind-numbingly simple. Then I create a composite event to detect when the user starts dragging on the UIElement (this is all used by a custom control of mine, the exact nature of which isn't particularly relevant). First here's a little helper function to see if the user's dragged the minimum drag distance:
private static bool MinimumDragSeen(Point start, Point end)
{
return Math.Abs(end.X - start.X) >= SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(end.Y - start.Y) >= SystemParameters.MinimumVerticalDragDistance;
}
And then the composite observable itself:
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseDrag(this UIElement element)
{
return Observable.CombineLatest(
element.ObserveMouseLeftButtonDown().Select(ep => ep.EventArgs.GetPosition(element)),
element.ObserveMouseMove().Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed),
(md, mm) => new { Down = md, Move = mm })
.Where(i => MinimumDragSeen(i.Down, i.Move.EventArgs.GetPosition(element)))
.Select(i => i.Move);
}
This all works nicely, after much gnashing of teeth and annoyance that there aren't any decent examples for Rx-based drag and drop with WPF controls (most of them are naively simplistic duplicates of dragging an image around in a canvas).
The problem comes when the window is maximized, specifically by double-clicking on the title bar. If, in the maximized layout, one of my controls which is subscribed to its own ObserveMouseDrag() ends up under the mouse cursor it receives a MouseLeftButtonDown and a MouseMove, one of them apparently before the maximize and the other one afterwards. The net result is that it starts a drag event, which then immediately stops as the mouse button has been released, and then tends to cause the control to drop onto itself, which in some situations in this app actually does something.
This is extremely odd, because I do not see why I should be receiving MouseDown and MouseMove caused by a double-click maximize. Surely all the mouse events involved in that should be handled by Windows, because I'm not using custom window borders or anything like that.
So, does anybody have any ideas?
The following day...
Fixed it! (With some help from Lee's answer below, and this question: What is the proper way to determine the end of a mouse drag using Rx?)
The code now looks like this:
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseDrag(this UIElement element)
{
var mouseDown = element.ObserveMouseLeftButtonDown().Select(ep => ep.EventArgs.GetPosition(element));
var mouseMove = element.ObserveMouseMove();
var stop = Observable.Merge(
element.ObserveMouseUp(),
element.ObserveMouseLeave().Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed)
);
return mouseDown.SelectMany(
md => mouseMove
.Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed)
.Where(ep => MinimumDragSeen(md, ep.EventArgs.GetPosition(element)))
.TakeUntil(stop)
);
}
And handles minimum drag distance, mouse up events and all kinds of things necessary for drag to work entirely properly. The maximize bug has gone away as well (although I still don't entirely understand that one, I suspect the mouse up handling might have something to do with it).
The key thing here is using SelectMany to handle multiple streams of events from mouseMove until either mouseUp in the control, or the mouse pointer leaving it with the mouse button down.