32

用于解释响应式扩展 (Rx) 功能的主要示例之一是将现有的鼠标事件组合成一个新的“事件”,表示鼠标拖动期间的增量:

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;

资料来源: Matthew Podwysocki 的 Reactive Framework 系列简介

在 MVVM 中,我通常会努力让我的 .xaml.cs 文件尽可能地为空,并且将视图中的事件与视图模型中的命令完全在标记中挂钩的一种方法是使用一种行为:

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

资料来源:布赖恩·杰尼西奥

反应式框架似乎更适合传统的 MVC 模式,其中控制器知道视图并可以直接引用其事件。

但是,我既想吃蛋糕又想吃!

你会如何结合这两种模式?

4

5 回答 5

40

我编写了一个框架来代表我在这个问题中的探索,称为ReactiveUI

它实现了 Observable ICommand,以及通过 IObservable 发出信号更改的 ViewModel 对象,以及将 IObservable“分配”给属性的能力,然后当 IObservable 更改时,属性将触发 INotifyPropertyChange。它还封装了很多常见的模式,比如让 ICommand 在后台运行任务,然后将结果编组回 UI。

我现在的文档绝对为零,但我将在未来几天内努力添加这些信息,以及我编写的示例应用程序

更新:我现在有很多文档,请查看http://www.reactiveui.net

于 2010-06-15T06:00:12.023 回答
8

我的问题的解决方案原来是创建一个实现 ICommand 和 IObservable<T> 的类

ICommand 用于绑定 UI(使用行为),然后可以在视图模型中使用 IObservable 来构造复合事件流。

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

Observable<T> 显示在从头开始实现 IObservable 中

于 2009-11-25T19:45:33.637 回答
7

当我开始思考如何将 MVVM 和 RX “结合”起来时,我首先想到的是一个 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);
    }
}

但后来我认为将控件绑定到 ICommand 属性的“标准”MVVM 方式不是很 RX'ish,它将事件流分解为相当静态的耦合。RX 更多的是关于事件,并且监听一个Executed路由事件似乎是合适的。这是我想出的:

1)你有一个命令中继行为,你安装在每个用户控件的根目录下,它应该响应命令:

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继承自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)您不将控件绑定到 ICommand 属性,而是使用 RoutedCommand 的:

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

在 XAML 中:

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

因此,在您的 ViewModel 上,您可以以非常 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"));
    }

现在,您拥有路由命令的强大功能(您可以自由选择在层次结构中的任何甚至多个 ViewModel 上处理命令),此外,您对所有命令都有一个“单一流程”,这比单独的 IObservable 对 RX 更好.

于 2010-02-15T09:02:31.953 回答
3

这也应该可以通过 ReactiveFramework 完全实现。

唯一需要的更改是为此创建一个行为,然后将行为连接到命令。它看起来像:

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

只要意识到在这种情况下 EventCommand 的工作方式与 ReactiveFramework 的工作方式非常相似。尽管 EventCommand 的实现会被简化,但您不会真正看到差异。

EventCommand 已经为您提供了一个推送模型 - 当事件发生时,它会触发您的命令。这是 Rx 的主要使用场景,但它使实现变得简单。

于 2009-11-19T14:14:42.497 回答
0

我认为这个想法是创建一个事件“和弦”,在这种情况下可能是一个拖动操作,这会导致一个命令被调用?这将与您在代码隐藏中执行的方式几乎相同,但代码在行为中。例如,创建一个 DragBehavior,它使用 Rx 将 MouseDown/MouseMove/MouseUp 事件与调用来处理新“事件”的命令结合起来。

于 2009-11-19T20:40:02.640 回答