如何正确使用RoutingState.NavigateCommandFor <t>

时间:2015-07-21 23:45:40

标签: c# reactiveui

我试图遵循XamForms Playground中建立的视图模型导航模式,但遇到了一些麻烦,让它正常工作。

鉴于以下代码,您可以看到问题所在:

// Router = new RoutingState();
// Navigate = Router.NavigateCommandFor<MyViewModel>();

this.WhenAnyValue(x => x.ViewModel.Navigate)
    //.Do(x => x.CanExecuteObservable.Subscribe())
    .BindTo(this, x => x.NavigateButton.Command);

this.WhenAnyValue(x => x.ViewModel.Router)
    .BindTo(this, x => x.ViewHost.Router);

如果我们只是将Navigate命令绑定到按钮的Command对象,则它的初始CanExecute状态为false,并且永远不会对该初始状态进行调整。如果我取消注释上面的Do代码,那么我们强制要计算的初始状态,并且该按钮会自动启用。

显然,这对我来说看起来不正确,因为您可能期望初始CanExecute状态为真(至少在加载UI之后)。

更糟糕的是,这似乎是Heisenbug,因为如果您检查CanExecuteRoutingState.Navigate命令的MyViewModel.Navigate状态(使用{{ 1}}),它们将返回CanExecute(null)并且按钮将在继续时显示启用(注意您只需检查视图模型命令以启用按钮)。

我想正确应用这种模式,但我很困惑为什么这种看似简单的模式在这种情况下失败了。任何人都可以确认或否认这是正确的方法吗?

2 个答案:

答案 0 :(得分:0)

对这个问题做了一些更多的研究,我能够澄清一些困惑,但仍然没有完全回答这个问题。以下是我的发现:

首先,根据online documentation绑定命令的正确方法是

this.BindCommand(ViewModel, x => x.Navigate, x => x.NavigateButton);

我上面发布的代码示例应该是这样的,如果它在适当的(当前)ReactiveUI框架中:

this.BindCommand(ViewModel, x => x.Navigate, x => x.NavigateButton);
// note that I also used a "hack" binding for the router above, this is the correct binding strategy
this.OneWayBind(ViewModel, x => x.Router, x => x.ViewHost.Router);

为了(尽可能无缝地)为可路由视图模型创建导航命令,我在返回命令之前创建了一些执行CanExecuteObservable订阅的扩展方法(暂时带有数字后缀)。在这个时候,我不确定扩展方法的最佳选择是什么,但是我倾向于数字3和4,因为它们产生一个强类型的通用命令(与{相比,它提供了自动可观察性) {1}})。

IReactiveCommand

我仍然不确定哪些扩展程序最合适,或者为什么订阅// all the NavigateCommandFor extensions also provide the ability to inject the navigate command itself (the default Navigate, or NavigateAndReset), // the dependency resolver (still defaults to Locator.Current), and the type resolution contract public static partial class ExtensionMethods { // simple extension to inline the subscription public static T SubscribeToCommand<T>(this T This) where T : IReactiveCommand { This.CanExecuteObservable.Subscribe(); return This; } // this was my first attempt without actually analyzing the current NavigateCommandFor source public static IReactiveCommand<object> NavigateCommandFor1<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null) where T : IRoutableViewModel { navigationCommand = navigationCommand ?? This.Navigate; var ret = ReactiveCommand.CreateAsyncObservable(navigationCommand.CanExecuteObservable, _ => navigationCommand.ExecuteAsync((dependencyResolver ?? Locator.Current).GetService<T>(contract))); return ret.SubscribeToCommand(); } // After looking at the NavigateCommandFor source, this is minimalistic adaptation // except ajusting the return value to be the generic interface with an object type param public static IReactiveCommand<object> NavigateCommandFor2<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null) where T : IRoutableViewModel { navigationCommand = navigationCommand ?? This.Navigate; var ret = new ReactiveCommand<object>(navigationCommand.CanExecuteObservable, x => Observable.Return(x)); ret.Select(_ => (dependencyResolver ?? Locator.Current).GetService<T>(contract)).InvokeCommand(navigationCommand); return ret.SubscribeToCommand(); } // My attempt to optimize the return value to be strongly typed // I'm not sure if this has any implications public static IReactiveCommand<T> NavigateCommandFor3<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null) where T : IRoutableViewModel { navigationCommand = navigationCommand ?? This.Navigate; var ret = new ReactiveCommand<T>(navigationCommand.CanExecuteObservable, _ => Observable.Return((dependencyResolver ?? Locator.Current).GetService<T>(contract))); ret.InvokeCommand(navigationCommand); return ret.SubscribeToCommand(); } // The original source allows for un-registered view models to be new()'d // this extension provides that same ability, but again with a strongly typed return value public static IReactiveCommand<T> NavigateCommandFor4<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null) where T : IRoutableViewModel, new() { navigationCommand = navigationCommand ?? This.Navigate; var ret = new ReactiveCommand<T>(navigationCommand.CanExecuteObservable, _ => Observable.Return((T)((IRoutableViewModel)(dependencyResolver ?? Locator.Current).GetService<T>(contract) ?? new T()))); ret.InvokeCommand(navigationCommand); return ret.SubscribeToCommand(); } } 是必要的(我认为这是CanExecuteObservable中的错误),但上述情况可能如此被认为是我最初发布的问题的可行解决办法(但不是问题的答案,因为我仍然认为这不是ReactiveCommand正确的用法。)

答案 1 :(得分:0)

通过在构建反应命令时在observable上调用CanExecute,我找到了一种更简洁的方法来播种Skip(1)状态。这减少了扩展方法的一小部分代码,给出了以下内容:

// The key fix here is to Skip(1) on CanExecuteObservable so that we seed the state
// all the NavigateCommandFor extensions also provide the ability to inject the navigate command itself (the default Navigate, or NavigateAndReset), 
// the dependency resolver (still defaults to Locator.Current), and the type resolution contract
public static partial class ExtensionMethods
{
    // After looking at the NavigateCommandFor source, this is minimalistic adaptation 
    // except ajusting the return value to be the generic interface with an object type param
    public static IReactiveCommand<object> PatchedNavigateCommandFor<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null)
        where T : IRoutableViewModel
    {
        navigationCommand = navigationCommand ?? This.Navigate;

        var ret = new ReactiveCommand<object>(navigationCommand.CanExecuteObservable.Skip(1), x => Observable.Return(x));

        ret.Select(_ => (dependencyResolver ?? Locator.Current).GetService<T>(contract)).InvokeCommand(navigationCommand);

        return ret;
    }

    // My attempt to optimize the return value to be strongly typed
    // I'm not sure if this has any implications
    public static IReactiveCommand<T> StrongNavigateCommandFor<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null)
        where T : IRoutableViewModel
    {
        navigationCommand = navigationCommand ?? This.Navigate;

        var ret = new ReactiveCommand<T>(navigationCommand.CanExecuteObservable.Skip(1), _ => Observable.Return((dependencyResolver ?? Locator.Current).GetService<T>(contract)));

        ret.InvokeCommand(navigationCommand);

        return ret;
    }

    // The original source allows for un-registered view models to be new()'d
    // this extension provides that same ability, but again with a strongly typed return value
    public static IReactiveCommand<T> StrongNewNavigateCommandFor<T>(this RoutingState This, IReactiveCommand<object> navigationCommand = null, IDependencyResolver dependencyResolver = null, string contract = null)
        where T : IRoutableViewModel, new()
    {
        navigationCommand = navigationCommand ?? This.Navigate;

        var ret = new ReactiveCommand<T>(navigationCommand.CanExecuteObservable.Skip(1), _ => Observable.Return((T)((IRoutableViewModel)(dependencyResolver ?? Locator.Current).GetService<T>(contract) ?? new T())));

        ret.InvokeCommand(navigationCommand);

        return ret;
    }
}

我认为这是最恰当的答案,我还有posted an issue对ReactiveUI github项目的调查结果。