2009-11-19 65 views
27

其中一個主要的實施例被用來說明無功擴展的功率(Rx)的是現有鼠標事件組合成一個新的「事件」表示鼠標拖動期間增量:無功擴展(Rx)+ MVVM =?

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's Introduction to the Reactive Framework series

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

來源:Brian Genisio

Reactive Framework似乎更適合傳統的MVC模式,其中控制器知道視圖並可以直接引用它的事件。

但是,我想都有我的蛋糕和吃它!

你會如何結合這兩種模式?

+0

平臺? Silverlight的? – AnthonyWJones 2009-11-19 13:54:34

+5

安東尼:這很重要嗎? – 2009-11-19 14:22:47

回答

35

我寫的代表在這個問題叫我探索的框架ReactiveUI

它實現了兩個可觀察到的ICommand的,以及誰信號經過的IObservable的變化,以及對「分配的能力視圖模型對象「一個屬性的IObservable,只要其IObservable發生更改,它就會觸發INotifyPropertyChange。它還封裝了很多常見模式,例如擁有在後臺運行任務的ICommand,然後將結果收集回UI。

我有絕對的零文檔了現在,但我會努力增加在今後幾天的信息,以及我編寫了

更新示例應用程序:我現在有相當多的文檔上去了,退房http://www.reactiveui.net

+0

您的項目看起來很有趣,期待文檔和示例應用程序! – 2010-06-23 21:26:37

+0

http://blog.paulbetts.org/index.php/2010/06/22/reactivexaml-series-reactivecommand/是其中一個主要類的帖子,Reactive ICommand – 2010-06-24 00:24:25

+0

作爲經驗豐富的WPF開發者,我可以說Reactive UI背後的想法非常好,推薦! – Xcalibur 2011-05-05 03:44:06

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的主要使用場景,但它使實現變得簡單。

+0

我不只是在尋找推式模型 - 我知道這就是命令提供的。我正在尋找一種方法,將現有的事件合併到我的ViewModel中的新事件中,而不是在代碼隱藏中 – 2009-11-20 07:45:44

0

我認爲這個想法是創建一個事件「和絃」,在這種情況下可能是一個拖動操作,這會導致命令被調用?這與你在代碼隱藏中的做法幾乎相同,但是代碼在行爲中。例如,創建一個DragBehavior,它使用Rx將MouseDown/MouseMove/MouseUp事件與一個調用來處理新「事件」的命令結合使用。

+0

這是我最初的想法,如果您創建的新事件產生新行爲,可重複使用「足夠」。但我正在尋找一種更加靈活的一次性事件混合方式。 – 2009-11-20 07:48:28

7

到我的問題的解決方案被證明是創建用於實現二者的ICommand和的IObservable < T A類>

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

凡可觀察<牛逼>在Implementing IObservable from scratch

6

所示。當我開始想如何「下嫁」 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)你必須要安裝在每個用戶控件的根應該響應命令一個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)視圖模型服務於用戶控制

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; } } 
} 
0123從ReactiveViewModel繼承

而在XAML:

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

其結果是,在你的視圖模型,你可以聽的很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的命令),而且對於所有命令而言,對於RX來說都比單獨的IObservable更好。