MvvmCross for WPF:基础入门





5.00/5 (9投票s)
概述 MvvmCross 在 WPF 应用程序开发中的应用。
引言
MvvmCross
是一个跨平台 MVVM 框架,支持 Xamarin、UWP 和 WPF 应用程序的开发。它拥有一个“视图模型优先”的导航系统,能够基于指定的视图模型在不同视图之间进行导航。
在本文中,我将介绍该框架用于 WPF 应用程序开发的一些特性,包括对其导航系统的简要概述。请注意,本文并非 WPF 或 MVVM 的入门介绍,因此您应该具备这方面的知识才能轻松理解。
背景
在我之前的 文章 中,我介绍了使用 Prism 库进行 WPF 应用程序开发的一些基础知识。本文将介绍 MvvmCross
,并使用一个与 Prism 文章中的示例应用程序类似的应用程序。该应用程序显示一个包含员工详细信息的卡片视图,以及另一个显示特定员工更多详细信息的视图。您可以从 GitHub 克隆或下载示例项目。

示例应用程序包含两个项目:一个 .NET Standard 库项目和一个 WPF 应用(.NET Framework)项目。

MvvmCross
核心
“Core
”是一个 .NET Standard 库,它引用了 MvvmCross
NuGet 包,并构成了 MvvmCross
应用程序的核心。在这里,MvvmCross
[主要] 期望您定义模型、服务和视图模型,并在 MvvmCross
IoC 容器中进行类型注册。
继承 MvxApplication
的 App
类是 IoC 容器注册类型以及指定要首先导航到的视图模型的地方。
using MvvmCross;
using MvvmCross.ViewModels;
using StaffStuff.Core.Services;
using StaffStuff.Core.ViewModels;
namespace StaffStuff.Core
{
public class App : MvxApplication
{
public override void Initialize()
{
base.Initialize();
Mvx.IoCProvider.RegisterSingleton<IStaffData>(new StaffData());
RegisterAppStart<StaffViewModel>();
}
}
}
在 Initialize()
方法中,我正在注册一个 IStaffData
类型的服务,并指定 StaffViewModel
(或者更准确地说,是其对应的视图)应该首先显示。
MvvmCross
应用程序中的视图模型必须继承框架的 MvxViewModel
类。此类包含框架对 INotifyPropertyChanged
的实现以及一些您可以为特定目的覆盖的视图模型生命周期方法。
using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using StaffStuff.Core.Models;
using StaffStuff.Core.Services;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace StaffStuff.Core.ViewModels
{
public class StaffViewModel : MvxViewModel
{
private List<Employee> _employees;
public List<Employee> Employees
{
get => _employees;
set => SetProperty(ref _employees, value);
}
private readonly IStaffData _staffData;
private readonly IMvxNavigationService _navigationService;
public IMvxAsyncCommand<Employee>
EmployeeDetailsCommand => new MvxAsyncCommand<Employee>(StaffDetails);
public StaffViewModel
(IStaffData staffData, IMvxNavigationService navigationService)
{
_staffData = staffData;
_navigationService = navigationService;
}
private async Task StaffDetails(Employee employee)
{
await _navigationService.Navigate<StaffDetailsViewModel, Employee>(employee);
}
public override async Task Initialize()
{
await base.Initialize();
Employees = _staffData.GetEmployees();
}
}
}
重写的 Initialize()
方法在导航到视图模型后调用,您应该在此处调用任何可能阻塞的代码,这些代码应该在初始化时执行。
导航
MvvmCross
应用程序中的导航是通过框架的导航服务完成的。在 StaffViewModel
的 StaffDetails()
方法中,该服务用于导航并将一个参数传递给 StaffDetailsViewModel
。该参数是一个 Employee
对象。
using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using StaffStuff.Core.Models;
using System.Threading.Tasks;
namespace StaffStuff.Core.ViewModels
{
public class StaffDetailsViewModel : MvxViewModel<Employee>
{
private readonly IMvxNavigationService _navigationService;
public IMvxAsyncCommand GoBackCommand => new MvxAsyncCommand(GoBack);
public StaffDetailsViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
private Employee _employee;
public Employee Employee
{
get => _employee;
set => SetProperty(ref _employee, value);
}
public override void Prepare(Employee parameter)
{
Employee = parameter;
}
private async Task GoBack()
{
await _navigationService.Close(this);
}
}
}
当期望使用参数进行导航时,会使用重写的 Prepare(TParameter parameter)
方法。请注意,您应该仅使用此方法来获取传递给视图模型的参数。请勿在此方法中执行任何其他逻辑。如果需要这样做,请使用前面提到的 Initialize()
方法。
在 GoBack()
方法中,调用导航服务的 Close()
方法来关闭视图,从而触发导航回上一个视图。
平台(WPF)
在设置 MvvmCross
解决方案时,预期的做法是将特定于平台的代码放在与目标平台关联的项目中。在示例解决方案中,StaffStuff.WPF
项目包含特定于 WPF 应用程序的代码。该项目引用了 MvvmCross
WPF 平台 NuGet 包以及解决方案的核心项目。
将 WPF 项目设置为 MvvmCross
兼容的第一步是让 App.xaml.cs 继承自 MvxApplication
并调用所需的 MvvmCross
声明。
using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Platforms.Wpf.Views;
namespace StaffStuff.WPF
{
public partial class App : MvxApplication
{
protected override void RegisterSetup()
{
this.RegisterSetupType<MvxWpfSetup<Core.App>>();
}
}
}
您还需要对 App.xaml 进行必要的修改
<views:MvxApplication x:Class="StaffStuff.WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StaffStuff.WPF"
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;
assembly=MvvmCross.Platforms.Wpf"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</views:MvxApplication>
接下来,您需要对 MainWindow.cs 进行一些修改,使其继承 MvxWindow
。
using System;
using System.Windows;
using System.Windows.Media.Imaging;
using MvvmCross.Platforms.Wpf.Views;
namespace StaffStuff.WPF
{
public partial class MainWindow : MvxWindow
{
public MainWindow()
{
InitializeComponent();
...
}
}
}
相应的更改也必须对 MainWindow.xaml 进行。
<views:MvxWindow x:Class="StaffStuff.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;
assembly=MvvmCross.Platforms.Wpf"
xmlns:local="clr-namespace:StaffStuff.WPF"
mc:Ignorable="d"
Background="#FF0D2738"
WindowStartupLocation="CenterScreen"
Height="450" Width="800">
<Grid>
</Grid>
</views:MvxWindow>
视图(用户控件)也不能幸免这些编辑,必须继承 MvxWpfView
。
using MvvmCross.Platforms.Wpf.Views;
namespace StaffStuff.WPF.Views
{
public partial class StaffView : MvxWpfView
{
public StaffView()
{
InitializeComponent();
}
}
}
正如您可能已经猜到的那样,XAML 文件也必须做出相应的更改。
完成这些过程后,应用程序就可以运行了。MvvmCross
会将视图与其对应的视图模型关联起来,并在需要时进行相应的导航。
结论
如果您还没有,现在应该已经具备了一些知识,可以更深入地了解 MvvmCross 框架。在我之前的 文章 中我讲了 Prism,因此在这里提及我对两者的比较是很合适的。
Prism 似乎更容易使用:它有 Visual Studio 模板,可以轻松设置相关项目。另一方面,MvvmCross
似乎没有任何提供 WPF 项目模板的 VS 扩展。我尽力寻找,但未能找到,因此不得不手动输入所有 MvvmCross 的声明。
此外,我喜欢使用 MahApps 来自定义 WPF 应用程序窗口。在 Prism 示例应用中,我能够舒适地使用它,但在 MvvmCross
中,我只能满足于默认的系统窗口。这是因为 MainWindow
必须继承 MvxWindow
,而为了使用 MahApps,MainWindow
必须继承 MetroWindow
。由于 C# 不支持多重继承,MahApps 就无法使用了。我尝试寻找合适的解决方案但未成功,或许我将在评论中得到启发。
历史
- 2020 年 7 月 7 日:初次发布