When I started to think of how to "marry" MVVM and RX, the first thing I thought of was an 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);
}
}
But then I thought that the "standard" MVVM way of binding controls to ICommand's properties is not very RX'ish, it breaks the event flow into fairly static couplings. RX is more about events, and listening to an Executed routed event seems appropriate. Here is what I came up with:
1) You have a CommandRelay behavior which you install at the root of each user control which should respond to commands:
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 serving the user control is inherited from the 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) You do not bind controls to ICommand properties, but use RoutedCommand's instead:
public static class MyCommands
{
private static readonly RoutedUICommand _testCommand
= new RoutedUICommand();
public static RoutedUICommand TestCommand
{ get { return _testCommand; } }
}
And in XAML:
<Button x:Name="btn" Content="Test" Command="ViewModel:MyCommands.TestCommand"/>
As a result, on your ViewModel you can listen to the commands in a very RX way:
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"));
}
Now, you have the power of routed commands (you are free to choose to handle the command on any or even multiple ViewModels in the hierarchy), plus you have a "single flow" for all the commands which is nicier to RX than separate IObservable's.