65.9K
CodeProject 正在变化。 阅读更多。
Home

MvvmCross for WPF:基础入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2020年7月7日

CPOL

4分钟阅读

viewsIcon

22943

概述 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 容器中进行类型注册。

继承 MvxApplicationApp 类是 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 应用程序中的导航是通过框架的导航服务完成的。在 StaffViewModelStaffDetails() 方法中,该服务用于导航并将一个参数传递给 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 日:初次发布
© . All rights reserved.