问题
I am trying to handle a drag & drop interaction, which involves mouse down, mouse move, and mouse up.
Here is a simplified repro of my solution that:
- on mouse down, creates an ellipse and adds it to a canvas
- on mouse move, repositions the ellipse to follow the mouse
on mouse up, changes the colour of the canvas so that it's obvious which one you're dragging.
var mouseDown = Observable.FromEvent<MouseButtonEventArgs>(canvas, "MouseLeftButtonDown"); var mouseUp = Observable.FromEvent<MouseButtonEventArgs>(canvas, "MouseLeftButtonUp"); var mouseMove = Observable.FromEvent<MouseEventArgs>(canvas, "MouseMove"); Ellipse ellipse = null; var q = from start in mouseDown.Do(x => { // handle mousedown by creating a red ellipse, // adding it to the canvas at the right position ellipse = new Ellipse() { Width = 10, Height = 10, Fill = Brushes.Red }; Point position = x.EventArgs.GetPosition(canvas); Canvas.SetLeft(ellipse, position.X); Canvas.SetTop(ellipse, position.Y); canvas.Children.Add(ellipse); }) from delta in mouseMove.Until(mouseUp.Do(x => { // handle mouse up by making the ellipse green ellipse.Fill = Brushes.Green; })) select delta; q.Subscribe(x => { // handle mouse move by repositioning ellipse Point position = x.EventArgs.GetPosition(canvas); Canvas.SetLeft(ellipse, position.X); Canvas.SetTop(ellipse, position.Y); });
the XAML is simply
<Canvas x:Name="canvas"/>
There's a few things I don't like about this code, and I need help refactoring it :)
First of all: the mousedown and mouseup callbacks are specified as side effects. If two subscriptions are made to q
, they will happen twice.
Second, the mouseup callback is specified before the mousemove callback. This makes it a bit hard to read.
Thirdly, the reference to the ellipse seems to be in a silly place. If there's two subscriptions, that variable reference will get overwritten quite quickly. I'm sure that there should be some way we can leverage the let
keyword to introduce a variable to the linq expression that will mean the correct ellipse reference is available to both the mouse move and mouse up handlers
How would you write this code?
回答1:
To avoid subscrition side-effects, you should publish your observable. I think something like this would be Ok:
public MainWindow()
{
InitializeComponent();
var mouseDown = Observable
.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");
var mouseUp = Observable
.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");
var mouseMove = Observable
.FromEvent<MouseEventArgs>(this, "MouseMove");
var ellipses = mouseDown
.Select(args => new {
a = args,
el = new Ellipse
{
Width = 10, Height = 10, Fill = Brushes.Red
}})
.Publish();
ellipses
.Subscribe(elargs =>
{
var position = elargs.a.EventArgs.GetPosition(canvas);
Canvas.SetLeft(elargs.el, position.X);
Canvas.SetTop(elargs.el, position.Y);
canvas.Children.Add(elargs.el);
});
var elmove = from elargs in ellipses
from mm in mouseMove.TakeUntil(mouseUp)
select new { a = mm, el = elargs.el };
elmove.
Subscribe(elargs =>
{
var position = elargs.a.EventArgs.GetPosition(canvas);
Canvas.SetLeft(elargs.el, position.X);
Canvas.SetTop(elargs.el, position.Y);
});
var elmup = from elargs in ellipses
from mup in mouseUp
select elargs.el;
elmup.Subscribe(el => el.Fill = Brushes.Green);
ellipses.Connect();
}
来源:https://stackoverflow.com/questions/2574046/whats-the-best-way-to-structure-this-linq-to-events-drag-drop-code