2009-11-19 3 views
27

Одним из основных примеров используется, чтобы объяснить силу Reactive Extensions (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 файл, как пустой, как это возможно, и один из способов подключения событий с точки зрения с помощью команд в ViewModel чисто в разметке используют поведение:

<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

+5

Энтони: Это имеет значение? –

ответ

35

Я написал структуру, которая представляет свои изыскания в этом вопросе называется ReactiveUI

Он реализует как наблюдаемый ICommand, а также объекты ViewModel, которые сигнализируют изменения через IObservable, а также возможность «назначать «IObservable к свойству, которое затем будет запускать INotifyPropertyChange, когда изменяется его IObservable. Он также инкапсулирует множество общих шаблонов, например, имеет ICommand, который запускает задачу в фоновом режиме, а затем возвращает результат обратно в пользовательский интерфейс.

Я абсолютно нулевой документации прямо сейчас, но я буду работать над добавлением этой информации в ближайшие дни, а также пример приложения я кодированный до

UPDATE: теперь у меня есть довольно много документации, проверьте http://www.reactiveui.net

+0

Ваш проект выглядит интересным, с нетерпением жду документов и примера приложения! –

+0

http://blog.paulbetts.org/index.php/2010/06/22/reactivexaml-series-reactivecommand/ - это сообщение на одном из основных классов, Reactive ICommand –

+0

Как опытный разработчик WPF, я могу сказать идеи позади реактивного интерфейса очень хорошие, рекомендуется! – Xcalibur

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 уже предоставляет вам модель push - когда событие происходит, оно запускает вашу команду. Это основной сценарий использования Rx, но он делает реализацию простой.

+0

Я не просто ищу модель push - я знаю, что это команда. Я ищу способ комбинирования существующих событий в новых событиях в моей модели ViewModel, а не в коде. –

0

Я думаю, идея заключалась в том, чтобы создать событие «аккорд», в данном случае, возможно, операция перетаскивания, которая приводит к вызову команды? Это будет сделано почти так же, как вы делали бы это в коде, но с кодом в поведении. Например, создайте DragBehavior, которое использует Rx, чтобы комбинировать события MouseDown/MouseMove/MouseUp с командой, вызываемой для обработки нового «события».

+0

Это была моя первоначальная идея, и, возможно, стоит поместить ее в новое поведение, если новые события, которые вы создаете «многократно». Но я действительно ищу более гибкий способ одноразового микширования событий. –

7

Решение моей проблемы оказалось бы создать класс, который реализует как ICommand и IObservable <T>

ICommand используется для связывания пользовательского интерфейса (с помощью вариантов поведения) и 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); 
      } 
     } 
    } 
} 

Где Наблюдаемые <T> показан в 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); 
    } 
} 

Но тогда я подумал, что «стандартный» способ MVVM привязки элементов управления к свойствам ICommand не очень 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) 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")); 
    } 

Теперь у вас есть сила маршрутизации команд (вы можете свободно управлять командой на любом или даже нескольких ViewModels в иерархии), плюс у вас есть «единственный поток» для всех команд, которые являются более привлекательными для RX, чем отдельные IObservable.

Смежные вопросы