2009-11-19 38 views
27

Một trong những ví dụ chính được sử dụng để giải thích sức mạnh của Reactive Extensions (Rx) được kết hợp các sự kiện chuột hiện có thành một mới 'sự kiện' đại diện cho vùng đồng bằng trong suốt kéo chuột:Tiện ích mở rộng phản ứng (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; 

Nguồn: Matthew Podwysocki's Introduction to the Reactive Framework series.

Trong MVVM Tôi thường cố gắng giữ .xaml.cs tôi nộp như trống càng tốt và một cách để hooking lên sự kiện từ quan điểm với các lệnh trong viewmodel thuần túy trong đánh dấu đang sử dụng một hành vi:

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

Nguồn: Brian Genisio.

Khung phản ứng dường như được hướng đến mô hình MVC truyền thống trong đó trình điều khiển biết chế độ xem và có thể tham chiếu trực tiếp các sự kiện của nó.

Nhưng, tôi muốn cả hai đều có bánh và ăn nó!

Bạn sẽ kết hợp hai mẫu này như thế nào?

+0

Nền tảng? Silverlight? – AnthonyWJones

+5

Anthony: Có quan trọng không? –

Trả lời

35

Tôi đã viết một khuôn khổ mà đại diện cho cuộc thám hiểm của tôi trong câu hỏi này được gọi là ReactiveUI

Nó thực hiện cả một ICommand Quan sát, cũng như đối tượng ViewModel người là dấu hiệu thay đổi thông qua một IObservable, cũng như khả năng "chuyển nhượng "một IObservable cho một tài sản, những người sau đó sẽ cháy INotifyPropertyChange bất cứ khi nào thay đổi IObservable của nó. Nó cũng đóng gói rất nhiều mẫu phổ biến, như có một ICommand chạy Task trên nền, sau đó kết quả trả về giao diện người dùng.

tôi đã hoàn toàn không tài liệu lên ngay bây giờ, nhưng tôi sẽ làm việc trên thêm rằng thông tin trong những ngày sắp tới, cũng như một ứng dụng mẫu tôi đã mã hóa lên

UPDATE: tôi bây giờ có khá nhiều tài liệu hướng dẫn, hãy xem http://www.reactiveui.net

+0

Dự án của bạn trông thú vị, mong được các tài liệu và ứng dụng ví dụ! –

+0

http://blog.paulbetts.org/index.php/2010/06/22/reactivexaml-series-reactivecommand/ là một bài đăng trên một trong các lớp học chính, một ICommand phản ứng –

+0

Là một nhà phát triển WPF dày dạn, tôi có thể nói những ý tưởng đằng sau UI phản ứng rất tốt, được khuyến nghị! – Xcalibur

3

Điều này hoàn toàn có thể thực hiện được thông qua ReactiveFramework.

Thay đổi duy nhất được yêu cầu sẽ là tạo hành vi cho điều này, sau đó có hành vi treo lên Lệnh. Nó sẽ trông giống như sau:

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

Chỉ cần nhận ra rằng EventCommand đang hoạt động theo cách tương tự như cách ReactiveFramework hoạt động, trong trường hợp này. Bạn sẽ không thực sự thấy một sự khác biệt, mặc dù việc thực hiện EventCommand sẽ được đơn giản hóa.

EventCommand đã cung cấp mô hình đẩy cho bạn - khi sự kiện diễn ra, nó sẽ kích hoạt lệnh của bạn. Đó là kịch bản sử dụng chính cho Rx, nhưng nó làm cho việc triển khai thực hiện đơn giản.

+0

Tôi không chỉ tìm kiếm một mô hình đẩy - tôi biết đó là những gì lệnh cung cấp. Tôi đang tìm cách kết hợp các sự kiện hiện có vào các sự kiện mới trong ViewModel của tôi thay vì ở mã sau –

0

Tôi nghĩ ý tưởng là tạo một sự kiện "hợp âm", trong trường hợp này có thể thao tác kéo, dẫn đến lệnh được gọi? Điều này sẽ được thực hiện khá nhiều giống như cách bạn muốn làm điều đó trong codebehind, nhưng với mã trong một hành vi. Ví dụ, tạo một DragBehavior sử dụng Rx để kết hợp các sự kiện MouseDown/MouseMove/MouseUp với một lệnh gọi để xử lý "sự kiện" mới.

+0

Đó là ý tưởng ban đầu của tôi và có thể đáng để rắc rối trong hành vi mới nếu các sự kiện mới bạn tạo có thể tái sử dụng 'đủ'. Nhưng tôi thực sự đang tìm kiếm một cách thức linh hoạt hơn để trộn lẫn một sự kiện. –

7

Giải pháp cho vấn đề của tôi hóa ra là để tạo ra một lớp mà thực hiện cả hai ICommand và IObservable <T>

ICommand được sử dụng để ràng buộc các giao diện người dùng (sử dụng hành vi) và IObservable sau đó có thể được sử dụng trong tầm nhìn mô hình để tạo luồng sự kiện tổng hợp.

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

đâu Quan sát <T> được thể hiện trong Implementing IObservable from scratch

6

Khi tôi bắt đầu suy nghĩ làm thế nào để "kết hôn" MVVM và RX, điều đầu tiên tôi nghĩ đến là một 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); 
    } 
} 

Nhưng sau đó tôi nghĩ rằng "tiêu chuẩn" MVVM cách điều khiển ràng buộc để tài sản của ICommand không phải là rất RX'ish, nó phá vỡ sự kiện chảy vào khớp nối khá tĩnh.RX có nhiều thông tin hơn về sự kiện và nghe sự kiện định tuyến Executed có vẻ phù hợp. Dưới đây là những gì tôi đã đưa ra:

1) Bạn có một hành vi CommandRelay mà bạn cài đặt tại thư mục gốc của mỗi điều khiển người dùng mà nên đáp ứng với lệnh:

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 phục vụ điều khiển người dùng là kế thừa từ 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) Bạn không ràng buộc điều khiển để tính ICommand, nhưng sử dụng RoutedCommand thay vì:

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

Và trong XAML:

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

Kết quả là, trên ViewModel của bạn, bạn có thể nghe các lệnh một cách rất 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")); 
    } 

Bây giờ, bạn có sức mạnh của lệnh định tuyến (bạn được tự do lựa chọn để xử lý lệnh trên bất kỳ hoặc thậm chí nhiều ViewModels trong hệ thống phân cấp), cộng với bạn có một "dòng chảy duy nhất" cho tất cả các lệnh đó là tốt hơn để RX hơn IObservable riêng biệt.

Các vấn đề liên quan