Question

L'un des principaux exemples étant utilisés pour expliquer la puissance de Reactive Extensions (Rx) est la combinaison d'événements de souris existants en un nouveau représentant « événement » deltas lors de glisser la souris:

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;

Source: introduction de Matthew Podwysocki à la série-cadre réactive .

Dans MVVM je cherche généralement à garder mon dossier .xaml.cs aussi vide que possible et un moyen d'accrochage des événements de la vue avec des commandes dans le viewmodel purement dans le balisage utilise un comportement:

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

Source: Brian Genisio

.

Le cadre réactive semble être plus orienté vers le modèle MVC traditionnel où un contrôleur connaît la vue et peut faire référence à ses événements directement.

Mais, je veux à la fois avoir mon gâteau et le manger!

Comment voulez-vous combiner ces deux motifs?

Était-ce utile?

La solution

J'ai écrit un cadre qui représente mes explorations dans cette question appelle ReactiveUI

Il met en œuvre à la fois un Observable ICommand, ainsi que des objets ViewModel qui signalent des changements via un IObservable, ainsi que la capacité de « attribuer » un IObservable à une propriété, qui sera alors le feu INotifyPropertyChange chaque fois que ses modifications IObservable. Il englobe aussi beaucoup de modèles communs, comme avoir un ICommand qui exécute une tâche en arrière-plan, Marshalls ensuite le résultat à l'interface utilisateur.

J'ai absolument aucune documentation en ce moment, mais je vais travailler sur l'ajout de cette information au cours des prochains jours, ainsi qu'un exemple d'application que j'ai codé jusqu'à

Mise à jour: J'ai maintenant beaucoup de documentation, vérifiez http: // www .reactiveui.net

Autres conseils

La solution à mon problème avéré être de créer une classe qui implémente les deux ICommand et IObservable

ICommand est utilisé pour lier l'interface utilisateur (en utilisant les comportements) et IObservable peut ensuite être utilisé dans le modèle de vue de construire des flux d'événements composites.

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

Où Observable est indiqué dans mise en œuvre IObservable à partir de zéro

Quand j'ai commencé à penser à la façon de « marier » MVVM et RX, la première chose que je pensais était 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);
    }
}

Mais alors je pensais que la façon MVVM « standard » de liaison de contrôles aux propriétés de ICommand n'est pas très RX'ish, il brise l'événement se jettent dans des raccords assez statiques. RX est plus sur les événements, et d'écouter un rel="noreferrer"> événement routé semble approprié. Voici ce que je suis venu avec:

1) Vous avez un comportement CommandRelay que vous installez à la racine de chaque contrôle utilisateur qui doit répondre aux commandes:

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 servant au contrôle utilisateur est héritée 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) Vous ne lient pas les contrôles aux propriétés ICommand, mais au lieu d'utiliser de RoutedCommand:

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

Et en XAML:

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

En conséquence, sur votre ViewModel vous pouvez écouter les commandes d'une manière très 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"));
    }

Maintenant, vous avez le pouvoir de commandes routés (vous êtes libre de choisir de gérer la commande sur tout ou même plusieurs ViewModels dans la hiérarchie), plus vous avez un « flux unique » pour toutes les commandes qui est nicier à RX que IObservable séparé de.

Cela devrait être parfaitement faisable via le ReactiveFramework, ainsi.

Le seul changement nécessaire serait de créer un comportement pour cela, ont alors le comportement crochet jusqu'à la commande. Il ressemblerait à quelque chose comme:

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

Il suffit de se rendre compte que EventCommand fonctionne d'une manière très similaire à la façon dont le ReactiveFramework fonctionnerait, dans ce scénario. Vous ne verrez pas vraiment une différence, même si la mise en œuvre de EventCommand serait simplifiée.

EventCommand fournit déjà un modèle push pour vous - lorsque l'événement se produit, il se déclenche votre commande. C'est le principal scénario d'utilisation pour Rx, mais il est simple de mise en œuvre.

Je pense que l'idée était de créer un événement « accord », dans ce cas, une opération de glissement probablement, ce qui se traduit par une commande appelée? Cela serait fait à peu près de la même façon que vous le feriez dans le behind, mais avec le code dans un comportement. Par exemple, créer un DragBehavior qui utilise Rx pour combiner les événements MouseDown / MouseMove / MouseUp avec une commande appelée à gérer le nouveau « événement ».

scroll top