如何在ReactiveUI 6.5

时间:2016-07-07 23:34:20

标签: winforms reactiveui

我是目前维护使用WinForms UI编写的大型应用程序套件的开发团队之一。

为了提高应用程序的可测试性,我们希望转向MVVM样式,将UI与业务逻辑分开。但是,我们需要继续使用WinForms UI,以最大限度地减少对用户使用套件中不同应用程序的影响。

在试用ReactiveUI时,我已经掌握了如何将表单控件和命令绑定到我的视图模型,但找不到有关如何弹出模式表单以请求或显示其他信息的文档或示例。例如,这些关于路由的文档页面提到了除WinForms之外的所有受支持的UI框架:http://docs.reactiveui.net/en/user-guide/routing/index.htmlhttps://github.com/reactiveui/ReactiveUI/blob/docs/docs/basics/routing.md

不幸的是,ReactiveUI“好的示例页面”似乎没有任何基于WinForms的示例,我可以使用Google找到的所有其他ReactiveUI / WinForms示例只是一种形式。

我绝对希望保留视图模型中的表单/视图以保持可测试性。

我认为正确的方法是在视图中使用由某些用户操作触发的ReactiveCommand(例如单击按钮,选择菜单项),但是:

  • 该命令应该做什么?

  • 即使文档中没有提到WinForms,它是否应该使用Routing?如果是,如何在WinForms应用程序中完成路由?

  • 命令/路由请求如何以模态方式显示新表单?

1 个答案:

答案 0 :(得分:1)

对于简单的消息和是/否答案,我会看看Wayne Maurer使用UserError的例子。我在Winform项目中使用了他的例子。

对于更复杂的东西,我在查找任何Winforms路由示例时遇到了同样的困难。我的谷歌搜索终于让我进入了ReactiveUI.Winforms的源代码,在那里我发现Paul已经拥有一个用于Winforms的UserControl来托管路由的UserControl视图。它被称为RoutedControlHost

使用该代码,我一起破解了一些可以显示模态形式的东西。我确信这不是最好的方法,但它可能会给你一些想法。

<强> RoutedModalHost

using Microsoft.Win32.SafeHandles;
using ReactiveUI;
using System;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ReactiveUI_Test_Routing
{
    public class RoutedModalHost : ReactiveObject, IDisposable
    {
        readonly CompositeDisposable disposables = new CompositeDisposable();

    RoutingState _Router;
    IObservable<string> viewContractObservable;        

    public RoutedModalHost()
    {

        this.ViewContractObservable = Observable.Return(default(string));

        var vmAndContract =
            this.WhenAnyObservable(x => x.Router.CurrentViewModel)
                .CombineLatest(this.WhenAnyObservable(x => x.ViewContractObservable),
                    (vm, contract) => new { ViewModel = vm, Contract = contract });

        Form viewLastAdded = null;
        this.disposables.Add(vmAndContract.Subscribe(x => {

            if (viewLastAdded != null)
            {
                viewLastAdded.Dispose();
            }

            if (x.ViewModel == null)
            {                    
                return;
            }

            IViewLocator viewLocator = this.ViewLocator ?? ReactiveUI.ViewLocator.Current;
            IViewFor view = viewLocator.ResolveView(x.ViewModel, x.Contract);
            view.ViewModel = x.ViewModel;

            viewLastAdded = (Form)view;
            viewLastAdded.ShowDialog();
        }, RxApp.DefaultExceptionHandler.OnNext));
    }

    [Category("ReactiveUI")]
    [Description("The router.")]
    public RoutingState Router
    {
        get { return this._Router; }
        set { this.RaiseAndSetIfChanged(ref this._Router, value); }
    }

    [Browsable(false)]
    public IObservable<string> ViewContractObservable
    {
        get { return this.viewContractObservable; }
        set { this.RaiseAndSetIfChanged(ref this.viewContractObservable, value); }
    }

    [Browsable(false)]
    public IViewLocator ViewLocator { get; set; }

    bool disposed = false;
    SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            handle.Dispose();
            // Free any other managed objects here.
            //
            this.disposables.Dispose();
        }

        // Free any unmanaged objects here.
        //
        disposed = true;
    }

}
}

<强> MainViewModel

using ReactiveUI;
using System.Reactive.Linq;
using System;    

namespace ReactiveUI_Test_Routing
{
public class MainViewModel : ReactiveObject, IScreen
{
    public RoutingState Router { get; private set; }
    public ReactiveCommand<object> ShowTestModalForm { get; protected set; }

    public MainViewModel(RoutingState modalRouter)
    {
        Router = modalRouter;

        ShowTestModalForm = ReactiveCommand.Create();
        ShowTestModalForm.Subscribe(x => Router.Navigate.Execute(new TestModalFormViewModel(this)));
    }

}
}

<强>的MainView

using System.Windows.Forms;
using Splat;
using ReactiveUI;

namespace ReactiveUI_Test_Routing
{
    public partial class MainView : Form, IViewFor<MainViewModel>
    {
        public MainView()
        {
            InitializeComponent();

        IMutableDependencyResolver dependencyResolver = Locator.CurrentMutable;

        dependencyResolver.Register(() => new TestModalFormView(), typeof(IViewFor<TestModalFormViewModel>));

        RoutingState router = new RoutingState();            

        RoutedModalHost modalHost = new RoutedModalHost();
        modalHost.Router = router;

        this.BindCommand(ViewModel, vm => vm.ShowTestModalForm, v => v.ShowTestModalForm);

        ViewModel = new MainViewModel(router);

    }

    public MainViewModel ViewModel { get; set; }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (MainViewModel)value; }            
    }
}
}

<强> TestModalFormViewModel

using ReactiveUI;

namespace ReactiveUI_Test_Routing
{
    public class TestModalFormViewModel : ReactiveObject, IRoutableViewModel
    {
        public IScreen HostScreen { get; protected set; }

    public string UrlPathSegment { get { return "ModalForm"; } }

    public TestModalFormViewModel(IScreen screen)
    {
        HostScreen = screen;
    }
}
}