diff --git a/src/ReactiveUI.Tests/CommandBindingTests.cs b/src/ReactiveUI.Tests/CommandBindingTests.cs index b94d5b243c..b4f32d1256 100755 --- a/src/ReactiveUI.Tests/CommandBindingTests.cs +++ b/src/ReactiveUI.Tests/CommandBindingTests.cs @@ -19,7 +19,7 @@ public FakeViewModel() Cmd = ReactiveCommand.Create(() => { }); } } - + public class FakeView : IViewFor { public TextBox TheTextBox { get; protected set; } @@ -138,6 +138,13 @@ public CommandBindViewModel() } public FakeNestedViewModel NestedViewModel { get; set; } + + private int value; + public int Value + { + get { return value; } + set { this.RaiseAndSetIfChanged(ref this.value, value); } + } } public class FakeNestedViewModel : ReactiveObject @@ -150,23 +157,30 @@ public FakeNestedViewModel() public ReactiveCommand NestedCommand { get; protected set; } } + public class CustomClickButton : Button + { + public event EventHandler CustomClick; + + public void RaiseCustomClick() => + this.CustomClick?.Invoke(this, EventArgs.Empty); + } public class CommandBindView : IViewFor { - object IViewFor.ViewModel { + object IViewFor.ViewModel { get { return ViewModel; } - set { ViewModel = (CommandBindViewModel)value; } + set { ViewModel = (CommandBindViewModel)value; } } public CommandBindViewModel ViewModel { get; set; } - public Button Command1 { get; protected set; } + public CustomClickButton Command1 { get; protected set; } public Image Command2 { get; protected set; } public CommandBindView() { - Command1 = new Button(); + Command1 = new CustomClickButton(); Command2 = new Image(); } } @@ -191,7 +205,7 @@ public void CommandBindByNameWireup() disp.Dispose(); Assert.Null(view.Command1.Command); } - + [Fact] public void CommandBindNestedCommandWireup() { @@ -300,5 +314,45 @@ public void CommandBindToExplicitEventWireup() Assert.Equal(1, invokeCount); } #endif + + [Fact] + public void CommandBindWithParameterExpression() + { + var vm = new CommandBindViewModel(); + var view = new CommandBindView() { ViewModel = vm }; + + var received = 0; + var cmd = ReactiveCommand.Create(i => { received = i; }); + vm.Command1 = cmd; + + var disp = view.BindCommand(vm, x => x.Command1, x => x.Command1, x => x.Value, nameof(CustomClickButton.CustomClick)); + + vm.Value = 42; + view.Command1.RaiseCustomClick(); + Assert.Equal(42, received); + + vm.Value = 13; + view.Command1.RaiseCustomClick(); + Assert.Equal(13, received); + } + + [Fact] + public void CommandBindWithParameterObservable() + { + var vm = new CommandBindViewModel(); + var view = new CommandBindView() { ViewModel = vm }; + + var received = 0; + var cmd = ReactiveCommand.Create(i => { received = i; }); + vm.Command1 = cmd; + + var value = Observable.Return(42); + var disp = view.BindCommand(vm, x => x.Command1, x => x.Command1, value, nameof(CustomClickButton.CustomClick)); + + vm.Value = 42; + view.Command1.RaiseCustomClick(); + + Assert.Equal(42, received); + } } } diff --git a/src/ReactiveUI/CommandBinding.cs b/src/ReactiveUI/CommandBinding.cs index e97205e4d6..f393a899ec 100755 --- a/src/ReactiveUI/CommandBinding.cs +++ b/src/ReactiveUI/CommandBinding.cs @@ -17,37 +17,9 @@ static CommandBinder() { RxApp.EnsureInitialized(); - binderImplementation = Locator.Current.GetService() ?? + binderImplementation = Locator.Current.GetService() ?? new CommandBinderImplementation(); } - - /// - /// Bind a command from the ViewModel to an explicitly specified control - /// on the View. - /// - /// A class representing the binding. Dispose it to disconnect - /// the binding - /// The View - /// The View model - /// The name of the control on the view - /// The ViewModel command to Bind. - /// The ViewModel property to pass as the - /// param of the ICommand - /// If specified, bind to the specific event - /// instead of the default. - public static IReactiveBinding BindCommand( - this TView view, - TViewModel viewModel, - Expression> propertyName, - Expression> controlName, - Func withParameter, - string toEvent = null) - where TViewModel : class - where TView : class, IViewFor - where TProp : ICommand - { - return binderImplementation.BindCommand(viewModel, view, propertyName, controlName, withParameter, toEvent); - } /// /// Bind a command from the ViewModel to an explicitly specified control @@ -59,14 +31,14 @@ public static IReactiveBinding BindCommandThe View model /// The name of the control on the view /// The ViewModel command to Bind. - /// The ViewModel property to pass as the + /// The ViewModel property to pass as the /// param of the ICommand - /// If specified, bind to the specific event + /// If specified, bind to the specific event /// instead of the default. public static IReactiveBinding BindCommand( - this TView view, - TViewModel viewModel, - Expression> propertyName, + this TView view, + TViewModel viewModel, + Expression> propertyName, Expression> controlName, IObservable withParameter, string toEvent = null) @@ -86,12 +58,12 @@ public static IReactiveBinding BindCommandThe View /// The View model /// The name of the control on the view - /// If specified, bind to the specific event + /// If specified, bind to the specific event /// instead of the default. public static IReactiveBinding BindCommand( - this TView view, - TViewModel viewModel, - Expression> propertyName, + this TView view, + TViewModel viewModel, + Expression> propertyName, Expression> controlName, string toEvent = null) where TViewModel : class @@ -100,7 +72,7 @@ public static IReactiveBinding BindCommand /// Bind a command from the ViewModel to an explicitly specified control /// on the View. @@ -110,14 +82,14 @@ public static IReactiveBinding BindCommandThe View /// The View model /// The name of the control on the view - /// The ViewModel property to pass as the + /// The ViewModel property to pass as the /// param of the ICommand - /// If specified, bind to the specific event + /// If specified, bind to the specific event /// instead of the default. public static IReactiveBinding BindCommand( - this TView view, - TViewModel viewModel, - Expression> propertyName, + this TView view, + TViewModel viewModel, + Expression> propertyName, Expression> controlName, Expression> withParameter, string toEvent = null) @@ -132,9 +104,9 @@ public static IReactiveBinding BindCommand BindCommand( - TViewModel viewModel, - TView view, - Expression> propertyName, + TViewModel viewModel, + TView view, + Expression> propertyName, Expression> controlName, Func withParameter, string toEvent = null) @@ -143,9 +115,9 @@ IReactiveBinding BindCommand BindCommand( - TViewModel viewModel, - TView view, - Expression> propertyName, + TViewModel viewModel, + TView view, + Expression> propertyName, Expression> controlName, IObservable withParameter, string toEvent = null) @@ -154,12 +126,12 @@ IReactiveBinding BindCommand BindCommand( - TViewModel viewModel, - TView view, - Expression> vmProperty, + TViewModel viewModel, + TView view, + Expression> vmProperty, Expression> controlProperty, Func withParameter, string toEvent = null) @@ -171,11 +143,11 @@ public IReactiveBinding BindCommand(); - IDisposable bindingDisposable = bindCommandInternal(source, view, controlExpression, Observable.Empty(), toEvent, cmd => { + IDisposable bindingDisposable = bindCommandInternal(source, view, controlExpression, Observable.Defer(() => Observable.Return(withParameter())), toEvent, cmd => { var rc = cmd as ReactiveCommand; if (rc == null) { return new RelayCommand(cmd.CanExecute, _ => cmd.Execute(withParameter())); - } + } var ret = ReactiveCommand.Create(() => ((ICommand)rc).Execute(null), rc.CanExecute); return ret; @@ -186,9 +158,9 @@ public IReactiveBinding BindCommand BindCommand( - TViewModel viewModel, - TView view, - Expression> vmProperty, + TViewModel viewModel, + TView view, + Expression> vmProperty, Expression> controlProperty, IObservable withParameter, string toEvent = null) @@ -202,14 +174,14 @@ public IReactiveBinding BindCommand(view, viewModel, controlExpression, vmExpression, + return new ReactiveBinding(view, viewModel, controlExpression, vmExpression, source, BindingDirection.OneWay, bindingDisposable); } IDisposable bindCommandInternal( IObservable This, - TView view, - Expression controlExpression, + TView view, + Expression controlExpression, IObservable withParameter, string toEvent, Func commandFixuper = null) @@ -242,7 +214,7 @@ IDisposable bindCommandInternal( return Disposable.Create(() => { propSub.Dispose(); disp.Dispose(); - }); + }); } } @@ -250,9 +222,9 @@ static class CommandBinderImplementationMixins { public static IReactiveBinding BindCommand( this ICommandBinderImplementation This, - TViewModel viewModel, - TView view, - Expression> propertyName, + TViewModel viewModel, + TView view, + Expression> propertyName, Expression> controlName, string toEvent = null) where TViewModel : class @@ -264,9 +236,9 @@ public static IReactiveBinding BindCommand BindCommand( this ICommandBinderImplementation This, - TViewModel viewModel, - TView view, - Expression> propertyName, + TViewModel viewModel, + TView view, + Expression> propertyName, Expression> controlName, Expression> withParameter, string toEvent = null) @@ -280,7 +252,7 @@ public static IReactiveBinding BindCommand bindCommandCache = + static readonly MemoizingMRUCache bindCommandCache = new MemoizingMRUCache((t, _) => { return Locator.Current.GetServices() .Aggregate(Tuple.Create(0, (ICreatesCommandBinding)null), (acc, x) => { @@ -289,7 +261,7 @@ class CreatesCommandBinding }).Item2; }, RxApp.SmallCacheLimit); - static readonly MemoizingMRUCache bindCommandEventCache = + static readonly MemoizingMRUCache bindCommandEventCache = new MemoizingMRUCache((t, _) => { return Locator.Current.GetServices() .Aggregate(Tuple.Create(0, (ICreatesCommandBinding)null), (acc, x) => {