Domanda

Uno dei principali esempi utilizzati per spiegare la potenza di reattivi Extensions (Rx) fa dell'incontro fra gli eventi del mouse esistenti in un nuovo 'evento' che rappresenta delta durante trascinamento del mouse:

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;

Fonte: Introduzione di Matteo Podwysocki alla serie quadro reattiva .

In MVVM Io generalmente sforzo di tenere i miei .xaml.cs file come vuoti come possibile e un modo di collegare gli eventi dal punto di vista con i comandi nel ViewModel puramente nel markup utilizza un comportamento:

<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>

Fonte: Brian Genisio

.

Il quadro Reactive sembra essere più orientato verso il modello tradizionale MVC cui un controllore conosce la vista e può fare riferimento direttamente gli eventi.

Ma, voglio entrambi hanno la mia torta e mangiarla!

Come si combinare questi due modelli?

È stato utile?

Soluzione

Ho scritto un quadro che rappresenta le mie esplorazioni in questa domanda chiamato ReactiveUI

Si implementa sia un ICommand osservabile, così come gli oggetti ViewModel che segnalano le modifiche tramite un IObservable, così come la capacità di "assegnare" un IObservable a una proprietà, che sarà poi il fuoco INotifyPropertyChange ogni volta che i suoi cambiamenti IObservable. Essa racchiude anche un sacco di modelli comuni, come avere un iCommand che gestisce un task in background, quindi marshalling il risultato di nuovo per l'interfaccia utente.

Non ho assolutamente documentazione zero fino adesso, ma lavorerò su aggiungendo che le informazioni nei prossimi giorni, così come un'applicazione di esempio che ho codificato up

UPDATE: Ora ho un bel po 'di documentazione up, controlla http: // www .reactiveui.net

Altri suggerimenti

La soluzione al mio problema si è rivelato essere quello di creare una classe che implementa sia ICommand e IObservable

ICommand viene utilizzato per legare l'interfaccia utente (utilizzando comportamenti) e IObservable può quindi essere utilizzato nel modello di vista di costruire flussi di eventi compositi.

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);
            }
        }
    }
}

Dove osservabile è mostrato in esecuzione IObservable da zero

Quando ho iniziato a pensare a come "sposarsi" MVVM e RX, la prima cosa che ho pensato è stato 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);
    }
}

Ma poi ho pensato che il modo MVVM "standard" di controlli vincolanti per le proprietà di ICommand non è molto RX'ish, si rompe il flusso di evento in innesti abbastanza statici. RX è di più su eventi, e l'ascolto di un rel="noreferrer"> evento indirizzato sembra opportuno. Ecco quello che mi si avvicinò con:

1) Si dispone di un comportamento CommandRelay cui si installa alla radice di ogni controllo utente, che dovrebbe rispondere ai comandi:

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 servendo il controllo utente viene ereditato dal 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) Non associare i controlli alle proprietà ICommand, ma l'uso di RoutedCommand invece:

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

E in XAML:

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

Di conseguenza, sul tuo ViewModel è possibile ascoltare i comandi in modo molto 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"));
    }

Ora, avete il potere di comandi instradati (siete liberi di scegliere di gestire il comando, su uno o anche più ViewModels nella gerarchia), più si ha un "unico flusso" per tutti i comandi che è nicier per RX che separati IObservable di.

Questo dovrebbe essere perfettamente fattibile attraverso il ReactiveFramework, pure.

L'unico cambiamento necessario sarebbe quello di creare un comportamento di questo, quindi avere il comportamento collegare al Comando. Sarebbe qualcosa di simile:

<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>

Basta rendersi conto che EventCommand sta lavorando in modo molto simile a come la ReactiveFramework avrebbe funzionato, in questo scenario. Non sarà veramente vedere la differenza, anche se l'attuazione di EventCommand verrebbe semplificata.

EventCommand già sta fornendo un modello push per voi - quando l'evento accade, spara il vostro comando. Questo è lo scenario di utilizzo principale per Rx, ma rende la semplice implementazione.

Credo che l'idea era quella di creare un evento "accordo", in questo caso un'operazione di trascinamento, probabilmente, che si traduce in un comando di essere chiamato? Questo sarebbe stato fatto più o meno allo stesso modo si farebbe nel codebehind, ma con il codice in un comportamento. Ad esempio, creare un DragBehavior che utilizza Rx di combinare gli eventi MouseDown / MouseMove / MouseUp con un comando chiamato per gestire il nuovo "evento".

scroll top