Pregunta

Uno de los principales ejemplos se utiliza para explicar el poder de extensiones reactivas (Rx) es combinar eventos de ratón existentes en un nuevo 'evento' que representa deltas durante arrastre del ratón:

var mouseMoves = from mm in mainCanvas.GetMouseMove()
                 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 mainCanvas.GetMouseLeftButtonDown()
                from md in mouseDiffs.Until(
                    mainCanvas.GetMouseLeftButtonUp())
                select md;

Fuente: Introducción de Matthew Podwysocki a la serie Marco Reactive .

En MVVM por lo general se esfuerzan por mantener mis .xaml.cs presentar lo más vacío posible y una manera de conectar los eventos desde el punto de vista con los comandos en el modelo de vista puramente en el marcado es el uso de un comportamiento:

<Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:EventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:EventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:EventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

Fuente: Brian Genisio

.

El Marco reactiva parece ser más orientado hacia el modelo tradicional MVC donde un controlador conoce la vista y puede hacer referencia a sus eventos directamente.

Pero, yo quiero tanto tener mi torta y comerla!

¿Cómo combinar estos dos patrones?

¿Fue útil?

Solución

He escrito un marco que representa mis exploraciones en esta pregunta se llama ReactiveUI

Se implementa tanto un ICommand observable, así como objetos ViewModel que indican cambios a través de una IObservable, así como la capacidad de "asignar" un IObservable a una propiedad, que a continuación se disparará INotifyPropertyChange siempre que cambie su IObservable. También engloba una gran cantidad de patrones comunes, como tener un ICommand que dirige una tarea en segundo plano, entonces marshalls el resultado de nuevo a la interfaz de usuario.

No tengo absolutamente documentación cero hasta este momento, pero voy a estar trabajando en la adición de que la información en los próximos días, así como una aplicación de ejemplo que he codificado por

ACTUALIZACIÓN: ahora tengo un buen montón de documentación, compruebe hacia fuera http: // www .reactiveui.net

Otros consejos

La solución a mi problema resultó ser la creación de una clase que implementa tanto ICommand y IObservable

ICommand se utiliza para enlazar la interfaz de usuario (usando comportamientos) y IObservable entonces se puede utilizar dentro del modelo de vista para construir secuencias de eventos compuestos.

using System;
using System.Windows.Input;

namespace Jesperll
{
    class ObservableCommand<T> : Observable<T>, ICommand where T : EventArgs
    {
        bool ICommand.CanExecute(object parameter)
        {
            return true;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { }
            remove { }
        }

        void ICommand.Execute(object parameter)
        {
            try
            {
                OnNext((T)parameter);
            }
            catch (InvalidCastException e)
            {
                OnError(e);
            }
        }
    }
}

Donde observable se muestra en la Ejecución IObservable desde cero

Cuando empecé a pensar en la forma de "casarse" MVVM y RX, lo primero que pensé fue en un ObservableCommand:

public class ObservableCommand : ICommand, IObservable<object>
{
    private readonly Subject<object> _subj = new Subject<object>();

    public void Execute(object parameter)
    {
        _subj.OnNext(parameter);
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public IDisposable Subscribe(IObserver<object> observer)
    {
        return _subj.Subscribe(observer);
    }
}

Pero luego pensé que la forma "estándar" MVVM de los controles de la unión a las propiedades de ICommand no es muy RX'ish, se rompe el flujo del evento en los acoplamientos bastante estáticos. RX es más acerca de los eventos, y escuchando a un rel="noreferrer"> evento enrutado parece apropiado. Esto es lo que ocurrió:

1) Usted tiene un comportamiento CommandRelay que se instala en la raíz de cada control de usuario que debe responder a los comandos:

public class CommandRelay : Behavior<FrameworkElement>
{
    private ICommandSink _commandSink;

    protected override void OnAttached()
    {
        base.OnAttached();
        CommandManager.AddExecutedHandler(AssociatedObject, DoExecute);
        CommandManager.AddCanExecuteHandler(AssociatedObject, GetCanExecute);
        AssociatedObject.DataContextChanged 
          += AssociatedObject_DataContextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        CommandManager.RemoveExecutedHandler(AssociatedObject, DoExecute);
        CommandManager.RemoveCanExecuteHandler(AssociatedObject, GetCanExecute);
        AssociatedObject.DataContextChanged 
          -= AssociatedObject_DataContextChanged;
    }

    private static void GetCanExecute(object sender, 
        CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void DoExecute(object sender, ExecutedRoutedEventArgs e)
    {
        if (_commandSink != null)
            _commandSink.Execute(e);
    }

    void AssociatedObject_DataContextChanged(
       object sender, DependencyPropertyChangedEventArgs e)

    {
        _commandSink = e.NewValue as ICommandSink;
    }
}

public interface ICommandSink
{
    void Execute(ExecutedRoutedEventArgs args);
}

2) ViewModel servir al control de usuario se hereda de la ReactiveViewModel:

    public class ReactiveViewModel : INotifyPropertyChanged, ICommandSink
    {
        internal readonly Subject<ExecutedRoutedEventArgs> Commands;

        public ReactiveViewModel()
        {
            Commands = new Subject<ExecutedRoutedEventArgs>();
        }

...
        public void Execute(ExecutedRoutedEventArgs args)
        {
            args.Handled = true;  // to leave chance to handler 
                                  // to pass the event up
            Commands.OnNext(args);
        }
    }

3) Usted no se unen a los controles propiedades ICommand, pero el uso de RoutedCommand lugar:

public static class MyCommands
{
    private static readonly RoutedUICommand _testCommand 
       = new RoutedUICommand();
    public static RoutedUICommand TestCommand 
      { get { return _testCommand; } }
}

Y en XAML:

<Button x:Name="btn" Content="Test" Command="ViewModel:MyCommands.TestCommand"/>

Como resultado de ello, en su modelo de vista se puede escuchar a los comandos de una manera muy RX:

    public MyVM() : ReactiveViewModel 
    {
        Commands
            .Where(p => p.Command == MyCommands.TestCommand)
            .Subscribe(DoTestCommand);
        Commands
            .Where(p => p.Command == MyCommands.ChangeCommand)
            .Subscribe(DoChangeCommand);
        Commands.Subscribe(a => Console.WriteLine("command logged"));
    }

Ahora, usted tiene el poder de comandos enrutados (que son libres de elegir para manejar el mando en cualquiera o incluso múltiples ViewModels en la jerarquía), además de contar con un "flujo único" para todos los comandos que es nicier de RX que aparte de IObservable.

Esto debería ser perfectamente factible a través de la ReactiveFramework, también.

El único cambio necesario sería la creación de un comportamiento de este, y luego tener el comportamiento conectar al comando. Se vería algo como:

<Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:ReactiveEventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:ReactiveEventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:ReactiveEventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

Sólo se dan cuenta de que EventCommand trabaja de una manera muy similar a cómo funcionaría el ReactiveFramework, en este escenario. Usted no va a ver realmente la diferencia, aunque la aplicación de EventCommand se simplificaría.

EventCommand ya está proporcionando un modelo de inserción para usted - cuando el evento ocurre, se dispara su comando. Ese es el principal escenario de uso de la receta, pero hace que la implementación simple.

Creo que la idea era crear un evento "acorde", en este caso una operación de arrastre, probablemente, lo que se traduce en un comando que se llama? Esto se haría más o menos la misma forma en que lo hace en el código subyacente, pero con el código de una conducta. Por ejemplo, crear un DragBehavior que utiliza Rx para combinar los eventos MouseDown / MouseMove / MouseUp con un comando llamado para manejar el nuevo "evento".

scroll top