Qual é a melhor maneira de estruturar esse código de arrastar e soltar Linq para eventos?
-
24-09-2019 - |
Pergunta
Estou tentando lidar com uma interação arrastar e soltar, que envolve o mouse para baixo, o movimento do mouse e o mouse para cima.
Aqui está uma representação simplificada da minha solução que:
- no mouse para baixo, cria uma elipse e a adiciona a uma tela
- No movimento do mouse, reposiciona a elipse para seguir o mouse
No mouse para cima, muda a cor da tela para que seja óbvio qual você está arrastando.
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); });
o xaml é simplesmente
<Canvas x:Name="canvas"/>
Há algumas coisas que eu não gosto nesse código, e preciso de ajuda para refatorá -lo :)
Primeiro de tudo: os retornos de chamada de Mousedown e Mouseup são especificados como efeitos colaterais. Se duas assinaturas são feitas para q
, eles vão acontecer duas vezes.
Segundo, o retorno de chamada do mouseup é especificado antes da o retorno de chamada do mousemove. Isso torna um pouco difícil de ler.
Em terceiro lugar, a referência à elipse parece estar em um lugar bobo. Se houver duas assinaturas, essa referência variável será substituída rapidamente. Tenho certeza de que deve haver alguma maneira de aproveitar o let
Palavra -chave para introduzir uma variável para a expressão LINQ que significa que a referência correta do elipse está disponível para os movimentos do mouse e os manipuladores de mouse para cima
Como você escreveria este código?
Solução
Para evitar efeitos colaterais da subscrição, você deve publicar seu observável. Eu acho que algo assim seria 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();
}