导航到 MvvmCross 中已经存在的视图模型

Navigating to already existing view model in MvvmCross

在我的应用中,我遇到过几种可以导航到已显示视图模型的情况:

  1. 在 macOS 上,应用程序首选项应显示在单独的 NSWindow 中,不会像在 UWP 或 ipadOS 中那样阻止或覆盖其他 windows。因此,用户可以打开首选项,然后保持打开状态(最小化或落后于其他 windows),然后使用 hotkey/menu/button 第二次打开它们。如何将 Navigate<SettingsViewModel>() 定向到 window 中已打开的视图而不是创建新视图?

  2. 我的应用程序具有类似于 IDE 的主从布局:左侧边栏中的大纲和右侧选项卡内的文档。用户可能打开某个文档,但随后在大纲中再次单击其名称,而不是通过打开的选项卡切换到该文档。我考虑通过 Navigate<DocViewModel, DocPathParam>(docPathParam) 打开新的文档选项卡,但在这种情况下如何捕捉已经打开的文档选项卡?

或者我应该避免在这两种情况下调用 Navigate() 方法,而是从特定平台的视图层检测打开的 windows 和选项卡?

在检查了 MvvmCross 的内部调用后,我得出结论,尝试“注入”到它的导航堆栈中太复杂了,我宁愿自己解决这两个问题。

对于第一种情况,我创建了一个小型“View Director”class,用作导航到单个实例 windows 的代理(类似于应用程序的设置)。虽然这种方法打破了 MvvmCross 的“从 VM 导航”原则,但我认为这很好,因为 windowing 行为无论如何都是特定于平台的。

当 View Director 收到导航请求时,它会检查自定义 UniqueWindowPresentation 属性,然后要求我的自定义 View Presenter 将焦点放在请求的视图模型的先前打开的 window 中。如果 Presenter 找不到这样的 window,则会发生常规 MvvmCross 导航(并最终创建 window)。

public class MvxMacViewDirector : IMvxMacViewDirector
{
    readonly IMvxViewsContainer _viewContainer;
    readonly IMvxAltMacViewPresenter _viewPresenter;
    readonly IMvxNavigationService _navigationService;

    public MvxMacViewDirector()
    {
        _viewContainer = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
        _viewPresenter = (IMvxAltMacViewPresenter)Mvx.IoCProvider.Resolve<IMvxViewPresenter>();
        _navigationService = Mvx.IoCProvider.Resolve<IMvxNavigationService>();
    }

    public void ShowView<TViewModel>() where TViewModel: MvxViewModel
    {
        Type viewType = _viewContainer.GetViewType(typeof(TViewModel));

        if (viewType.GetCustomAttribute<UniqueWindowPresentationAttribute>() != null)
        {
            if (_viewPresenter.ShowPreviouslyOpenedWindow<TViewModel>() == false)
                _navigationService.Navigate<TViewModel>();
        }
        else
            _navigationService.Navigate<TViewModel>();
    }
}

自定义 View Presenter 中的方法如下所示:

public bool ShowPreviouslyOpenedWindow<T>() where T : MvxViewModel
{
    foreach (var item in Windows)
    {
        if (item.ContentViewController is MvxViewController viewController &&
            viewController.ViewModel.GetType() == typeof(T))
        {
            item.MakeKeyAndOrderFront(null);
            return true;
        }
    }

    return false;
}

第二种情况确实让我思考了基于 MvvmCross 导航的合适用例。最后,我决定它不应该负责显示嵌套的 views/VMs(就像拆分视图的“详细信息”部分中的选项卡),因为这种行为过于依赖于内容,无法在 View Presenter 中概括它.相反,我直接从父窗格的视图模型管理选项卡的切换和创建:PaneViewModel 通过 IMvxViewModelLoader 创建选项卡 VM,同时 PaneView 观察其 VM 的选项卡集合并通过创建相应的视图IMvxMacViewCreator,然后给他们分配虚拟机。这与 MvvmCross 在内部实例化视图+VM 对所做的非常相似。

因此,我仅将 MvvmCross 导航用于需要替换 window 的全部内容或拆分视图的“详细信息”部分的“根视图”情况。或者当我需要在顶部显示 dialog/sheet 叠加层时。嵌套在这些视图中的所有内容都从它们的视图模型内部进行协调。