32

Reactive Extensions (Rx) の能力を説明するために使用されている主な例の 1 つは、既存のマウス イベントを、マウス ドラッグ中のデルタを表す新しい「イベント」に結合することです。

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 ファイルをできるだけ空に保つように努めています。ビューモデルのコマンドを使用してビューからイベントを純粋にマークアップでフックする 1 つの方法は、動作を使用することです。

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

出典:ブライアン・ジェニシオ

Reactive Framework は、コントローラーがビューを認識し、そのイベントを直接参照できる従来の MVC パターンにより適しているようです。

でも、ケーキも食べたい!

この 2 つのパターンをどのように組み合わせますか?

4

5 に答える 5

40

ReactiveUIと呼ばれるこの質問での探索を表すフレームワークを作成しました

Observable ICommand と、IObservable を介して変更を通知する ViewModel オブジェクト、および IObservable をプロパティに「割り当てる」機能の両方を実装します。IObservable が変更されるたびに INotifyPropertyChange を起動します。また、タスクをバックグラウンドで実行し、結果を UI にマーシャリングする ICommand など、多くの一般的なパターンをカプセル化します。

現在、作成しているドキュメントはまったくありませんが、今後数日間でその情報を追加し、コード化したサンプル アプリケーションを追加する予定です。

更新:私は今、かなり多くのドキュメントを作成しています。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 風ではなく、イベント フローをかなり静的な結合に分割すると考えました。RX はイベントに関するものであり、Executedルーティング イベントをリッスンするのが適切と思われます。これが私が思いついたものです:

1) コマンドに応答する各ユーザー コントロールのルートにインストールする CommandRelay 動作があります。

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

これで、ルーティングされたコマンドの力が得られます (階層内の任意のビューモデルまたは複数のビューモデルでコマンドを処理することを自由に選択できます)。さらに、すべてのコマンドに対して「単一のフロー」があり、個別の IObservable よりも RX に適しています。 .

于 2010-02-15T09:02:31.953 に答える
3

これは、ReactiveFramework を介しても完全に実行できるはずです。

必要な唯一の変更は、このための動作を作成し、その動作を Command に接続することです。次のようになります。

<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

このアイデアは、イベント「コード」を作成することだったと思います。この場合、おそらくドラッグ操作で、コマンドが呼び出されますか? これは分離コードで行うのとほぼ同じ方法で行われますが、ビヘイビア内のコードを使用します。たとえば、Rx を使用して MouseDown/MouseMove/MouseUp イベントを、新しい「イベント」を処理するために呼び出されるコマンドと組み合わせる DragBehavior を作成します。

于 2009-11-19T20:40:02.640 に答える