2009-11-19 19 views
27

Uno de los principales ejemplos que se utilizan para explicar el poder de extensiones reactivas (Rx) es la combinación de eventos de ratón existentes en un nuevo 'evento' que representa deltas durante arrastre del ratón:Extensiones reactivas (Rx) + MVVM =?

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: Matthew Podwysocki's Introduction to the Reactive Framework series.

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 de la vista con 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 reactivo parece estar más orientado hacia el patrón MVC tradicional donde un controlador conoce la vista y puede hacer referencia a sus eventos directamente.

Pero, ¡quiero tener mi pastel y comerlo!

¿Cómo combinaría estos dos patrones?

+0

Plataforma? Silverlight? – AnthonyWJones

+5

Anthony: ¿Importa? –

Respuesta

35

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

Implementa tanto un ICommand observable, así como objetos ViewModel que indican cambios a través de una IObservable, así como la capacidad de "asignar "IObservable a una propiedad, que luego activará INotifyPropertyChange siempre que cambie IObservable. También encapsula muchos patrones comunes, como tener un ICommand que ejecuta una Tarea en segundo plano, luego ordena el resultado a la IU.

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 hasta

ACTUALIZACIÓN: Ahora tengo un montón de documentación, echa un vistazo a http://www.reactiveui.net

+0

¡Su proyecto parece interesante, esperando los documentos y la aplicación de ejemplo! –

+0

http://blog.paulbetts.org/index.php/2010/06/22/reactivexaml-series-reactivecommand/ es una publicación en una de las clases principales, un ICommand reactivo –

+0

Como un experimentado desarrollador de WPF, puedo decir las ideas detrás de UI reactiva son muy buenas, ¡recomendado! – Xcalibur

3

Esto también debería ser posible a través del ReactiveFramework.

El único cambio requerido sería crear un comportamiento para esto y luego conectar el comportamiento con el 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> 

acaba de darse cuenta de que EventCommand trabaja de una manera muy similar a cómo funcionaría el ReactiveFramework, en este escenario. Realmente no verá la diferencia, aunque la implementación de EventCommand se simplificaría.

EventCommand ya proporciona un modelo de inserción para usted: cuando ocurre un evento, se apaga el comando. Ese es el escenario de uso principal para Rx, pero simplifica la implementación.

+0

No solo estoy buscando un modelo push: sé que eso es lo que proporciona el comando. Estoy buscando una manera de combinar eventos existentes en nuevos eventos dentro de mi ViewModel en lugar de en el código subyacente –

0

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

+0

Esa fue mi idea inicial y podría valer la pena envolverla en un nuevo comportamiento si los nuevos eventos que usted crea son reutilizables "suficiente". Pero realmente estoy buscando una forma más flexible de mezcla de eventos únicos. –

7

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

ICommand se utiliza para obligar a la interfaz de usuario (usando comportamientos) y IObservable continuación, se puede utilizar dentro de la vista modelo 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); 
      } 
     } 
    } 
} 

Cuando se indique observable <T> en Implementing IObservable from scratch

6

Cuando empecé a pensar en la forma de "casarse" MVVM y RX, lo primero que pensé fue 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 MVVM "estándar" de unir controles a las propiedades de ICommand no es muy RX'ish, divide el flujo de eventos en acoplamientos bastante estáticos.RX trata más sobre eventos, y escuchar un evento enrutado Executed 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) modelo de vista sirviendo al control de usuario es heredado 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, 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 (usted es libre de elegir manejar el comando en cualquiera o incluso múltiples modelos de vista en la jerarquía), además tiene un "flujo único" para todos los comandos que es más agradable para RX que para los de IObservable.

Cuestiones relacionadas