Pergunta

Um dos principais exemplos usados ​​para explicar o poder das Extensões Reativas (Rx) é combinar eventos de mouse existentes em um novo 'evento' representando deltas durante o arrasto do mouse:

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;

Fonte: Introdução à série Reactive Framework de Matthew Podwysocki.

No MVVM, geralmente me esforço para manter meu arquivo .xaml.cs o mais vazio possível e uma maneira de conectar eventos da visualização com comandos no modelo de visualização puramente na marcação é usar um comportamento:

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

Fonte: Brian Genísio.

O Reactive Framework parece ser mais voltado para o padrão MVC tradicional, onde um controlador conhece a visão e pode referenciar seus eventos diretamente.

Mas eu quero pegar meu bolo e comê-lo!

Como você combinaria esses dois padrões?

Foi útil?

Solução

Eu escrevi uma estrutura que representa minhas explorações nesta questão chamada UI reativa

Ele implementa um ICommand Observable, bem como objetos ViewModel que sinalizam alterações por meio de um IObservable, bem como a capacidade de "atribuir" um IObservable a uma propriedade, que então disparará INotifyPropertyChange sempre que seu IObservable for alterado.Ele também encapsula muitos padrões comuns, como ter um ICommand que executa uma tarefa em segundo plano e, em seguida, empacota o resultado de volta para a interface do usuário.

Não tenho absolutamente nenhuma documentação disponível no momento, mas trabalharei para adicionar essas informações nos próximos dias, bem como um aplicativo de exemplo que codifiquei

ATUALIZAR: Agora tenho bastante documentação, confira http://www.reactiveui.net

Outras dicas

A solução para o meu problema acabou sendo criar uma classe que implemente ICommand e IObservable<T>

ICommand é usado para vincular a UI (usando comportamentos) e IObservable pode então ser usado dentro do modelo de visualização para construir fluxos de eventos compostos.

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

Onde Observable<T> é mostrado em Implementando IObservable do zero

Quando comecei a pensar em como “casar” MVVM e RX, a primeira coisa que pensei foi em um 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);
    }
}

Mas então pensei que a maneira "padrão" do MVVM de vincular controles às propriedades do ICommand não é muito RX, pois divide o fluxo de eventos em acoplamentos bastante estáticos.RX é mais sobre eventos e ouvir um Executado evento roteado parece apropriado.Aqui está o que eu descobri:

1) Você tem um comportamento CommandRelay que instala na raiz de cada controle de usuário que deve responder aos comandos:

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 que atende ao controle do usuário é herdado do 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) Você não vincula controles às propriedades ICommand, mas usa RoutedCommand:

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

E em XAML:

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

Como resultado, no seu ViewModel você pode ouvir os comandos de uma forma bem 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"));
    }

Agora, você tem o poder dos comandos roteados (você é livre para escolher lidar com o comando em qualquer ou até mesmo em vários ViewModels na hierarquia), além de ter um "fluxo único" para todos os comandos que é mais agradável para RX do que IObservables separados .

Isso também deve ser perfeitamente possível por meio do ReactiveFramework.

A única mudança necessária seria criar um comportamento para isso e, em seguida, conectar o comportamento ao Comando.Seria algo como:

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

Basta perceber que EventCommand está funcionando de maneira muito semelhante a como o ReactiveFramework funcionaria, neste cenário.Você realmente não verá diferença, embora a implementação de EventCommand seja simplificada.

EventCommand já está fornecendo um modelo push para você - quando o evento acontece, ele dispara seu comando.Esse é o principal cenário de uso do Rx, mas simplifica a implementação.

Acho que a ideia era criar um evento "acorde", neste caso provavelmente uma operação de arrastar, que resulta na chamada de um comando?Isso seria feito praticamente da mesma maneira que você faria no codebehind, mas com o código em um comportamento.Por exemplo, crie um DragBehavior que use Rx para combinar os eventos MouseDown/MouseMove/MouseUp com um comando chamado para manipular o novo "evento".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top