导航到 MvvmCross 中已经存在的视图模型
Navigating to already existing view model in MvvmCross
在我的应用中,我遇到过几种可以导航到已显示视图模型的情况:
在 macOS 上,应用程序首选项应显示在单独的 NSWindow
中,不会像在 UWP 或 ipadOS 中那样阻止或覆盖其他 windows。因此,用户可以打开首选项,然后保持打开状态(最小化或落后于其他 windows),然后使用 hotkey/menu/button 第二次打开它们。如何将 Navigate<SettingsViewModel>()
定向到 window 中已打开的视图而不是创建新视图?
我的应用程序具有类似于 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 叠加层时。嵌套在这些视图中的所有内容都从它们的视图模型内部进行协调。
在我的应用中,我遇到过几种可以导航到已显示视图模型的情况:
在 macOS 上,应用程序首选项应显示在单独的
NSWindow
中,不会像在 UWP 或 ipadOS 中那样阻止或覆盖其他 windows。因此,用户可以打开首选项,然后保持打开状态(最小化或落后于其他 windows),然后使用 hotkey/menu/button 第二次打开它们。如何将Navigate<SettingsViewModel>()
定向到 window 中已打开的视图而不是创建新视图?我的应用程序具有类似于 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 叠加层时。嵌套在这些视图中的所有内容都从它们的视图模型内部进行协调。