English 中文(简体)
Reactive Extensions (Rx) + MVVM =?
原标题:

One of the main examples being used to explain the power of Reactive Extensions (Rx) is combining existing mouse events into a new event representing deltas during mouse drag:

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

In MVVM I generally strive to keep my .xaml.cs file as empty as possible and one way of hooking up events from the view with commands in the viewmodel purely in markup is using a behavior:

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

The Reactive Framework seems to be more geared towards the traditional MVC pattern where a controller knows the view and can reference its events directly.

But, I want to both have my cake and eat it!

How would you combine these two patterns?

最佳回答

I ve written a framework that represents my explorations in this question called ReactiveUI

It implements both an Observable ICommand, as well as ViewModel objects who signal changes via an IObservable, as well as the ability to "assign" an IObservable to a property, who will then fire INotifyPropertyChange whenever its IObservable changes. It also encapsulates a lot of common patterns, like having an ICommand who runs a Task in the background, then marshalls the result back to the UI.

I have absolutely zero documentation up right now, but I ll be working on adding that information over the coming days, as well as a sample application I ve coded up

UPDATE: I now have quite a lot of documentation up, check out http://www.reactiveui.net

问题回答

The solution to my problem turned out to be to create a class that implements both ICommand and IObservable<T>

ICommand is used to bind the UI (using behaviors) and IObservable can then be used within the view model to construct composite event streams.

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

Where Observable<T> is shown in Implementing IObservable from scratch

When I started to think of how to "marry" MVVM and RX, the first thing I thought of was an 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);
    }
}

But then I thought that the "standard" MVVM way of binding controls to ICommand s properties is not very RX ish, it breaks the event flow into fairly static couplings. RX is more about events, and listening to an Executed routed event seems appropriate. Here is what I came up with:

1) You have a CommandRelay behavior which you install at the root of each user control which should respond to commands:

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 serving the user control is inherited from the 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) You do not bind controls to ICommand properties, but use RoutedCommand s instead:

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

And in XAML:

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

As a result, on your ViewModel you can listen to the commands in a very RX way:

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

Now, you have the power of routed commands (you are free to choose to handle the command on any or even multiple ViewModels in the hierarchy), plus you have a "single flow" for all the commands which is nicier to RX than separate IObservable s.

This should be perfectly doable via the ReactiveFramework, as well.

The only change required would be to create a behavior for this, then have the behavior hook up to the Command. It would look something like:

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

Just realize that EventCommand is working in a very similar way to how the ReactiveFramework would work, in this scenario. You won t really see a difference, although the implementation of EventCommand would be simplified.

EventCommand already is providing a push model for you - when the event happens, it fires your command. That s the main usage scenario for Rx, but it makes the implementation simple.

I think the idea was to create an event "chord", in this case a drag operation probably, which results in a command being called? This would be done pretty much the same way you d do it in the codebehind, but with the code in a behavior. For example, create a DragBehavior that uses Rx to combine the MouseDown/MouseMove/MouseUp events with a command called to handle the new "event".





相关问题
Reactive Extensions (Rx) + MVVM =?

One of the main examples being used to explain the power of Reactive Extensions (Rx) is combining existing mouse events into a new event representing deltas during mouse drag: var mouseMoves = from ...

Where should the data be stored in MVVM?

I ve got this Silverlight Prism application that is using MVVM. The model calls a WCF service and a list of data is returned. The ViewModel is bound to the View, so the ViewModel should have a List ...

Where to put the calls to WCF or other webservices in MVVM?

I m building Silverlight applicaitions using Prism and MVVM. When calling WCF services on your own server, or even external webservices like the Bing api, would this be done from the Model? or from ...

How to avoid View specific code in my ViewModel

My application has a menu option to allow the creation of a new account. The menu option s command is bound to a command (NewAccountCommand) in my ViewModel. When the user clicks the option to create ...

WPF MVVM User Control binding issues

I have an application that uses MVVM. I have several items on the main window that bind to the ViewModel for that window. When I run it everything works. However, when I add a user control to the main ...

WPF: writing smoke tests using ViewModels

I am considering to write smoke tests for our WPF application. The question that I am faced is: should we use UI automation( or some other technology that creates a UI script), or is it good enough to ...

WPF - MVVM - NHibernate Validation

Im facing a bit of an issue when trying to validate a decimal property on domain object which is bound to a textbox on the view through the viewmodel. I am using NHibernate to decorate my property on ...

热门标签