Stylet框架在WPF-MVVM应用中的简化实现

Stylet是一个轻量级的框架,旨在简化WPF-MVVM应用的创建过程,通过减少实现某些MVVM特性所需的代码量。本文将简要介绍这些特性,但请注意,本文不是WPF或MVVM的入门介绍。如果想要舒适地跟随本文,应该已经了解这两个概念。

之前写过关于Prism和MvvmCross的文章,使用了一个展示一些虚构员工卡片的示例应用程序。本文延续了这一趋势:示例应用程序有卡片,上面有一些员工的详细信息,点击卡片会导航到显示特定员工更多详细信息的页面。

示例应用程序有三个项目:一个.NET CoreWPF应用程序项目和两个.NET Core类库项目;一个包含共享代码,另一个是Stylet模块。可以从GitHub克隆或下载示例项目。

Stylet根视图和导体

要在WPF应用程序项目中使用Stylet,必须引用Stylet NuGet包。然后,必须创建一个根视图和根视图模型,它们将根据命名约定相互关联。根视图,它必须是一个Window,作为应用程序的外壳。在示例项目中,ShellViewModel是根视图模型。

using Stylet; namespace StaffStuff.ViewModels { public class ShellViewModel : Conductor<IScreen>.StackNavigation { public ShellViewModel(StaffViewModel staffViewModel) { this.DisplayName = string.Empty; this.ActivateItem(staffViewModel); } } }

ShellViewModel派生自Stylet的Conductor<T>.StackNavigation。导体管理它拥有的视图模型的生命周期。它决定一个视图模型是被激活、停用还是关闭。Conductor<T>.StackNavigation是一个提供基于栈的导航的导体。在示例应用程序中,导体将启用从卡片视图到详细信息视图的导航。

在ShellViewModel的构造函数中,一个视图模型作为依赖项传递,并设置为活动项。由于ShellViewModel是一个导体,它现在拥有StaffViewModel,并将管理其生命周期。在ShellView中,活动项,或者说与活动项关联的视图,将被放置在一个ContentControl中。

<Window x:Class="StaffStuff.Views.ShellView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="https://github.com/canton7/Stylet" Background="#FF0D2738" Height="480" Width="800" WindowStartupLocation="CenterScreen"> <Grid> <ContentControl s:View.Model="{Binding ActiveItem}" /> </Grid> </Window>

ActiveItem是导体的一个属性,通过调用导体的ActivateItem()方法设置。

Bootstrapper

有了根视图和视图模型,Stylet要求创建一个bootstrapper,在这里指定根视图模型。

namespace StaffStuff { public class Bootstrapper : Bootstrapper<ShellViewModel> { protected override void ConfigureIoC(IStyletIoCBuilder builder) { builder.AddModule(new ServicesModule()); } } }

然后将Stylet的ApplicationLoader添加为应用程序资源,并设置bootstrapper为加载项。

<Application x:Class="StaffStuff.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:StaffStuff" xmlns:s="https://github.com/canton7/Stylet"> <Application.Resources> <s:ApplicationLoader> <s:ApplicationLoader.Bootstrapper> <local:Bootstrapper/> </s:ApplicationLoader.Bootstrapper> </s:ApplicationLoader> </Application.Resources> </Application>

除了设置根视图模型,bootstrapper也是可以在Stylet的IoC容器中注册类型的地方。这是在ConfigureIoC()中使用StyleIoCBuilder完成的,所以可以做类似builder.Bind<IStaffData>().To<StaffData>()的事情。在示例项目的bootstrapper中,向构建器添加了一个模块,builder.AddModule(new ServicesModule())。

模块

如引言中提到的,Stylet支持开发模块化的WPF-MVVM应用程序。要创建一个Stylet模块,创建一个.NET Core类库项目,引用Stylet NuGet包,并在项目的根目录添加一个派生自StyletIoCModule的类。然后可以在模块的Load()方法中将类型注册到IoC容器中。

using StaffStuff.Common.Interfaces; using StaffStuff.Services.Services; using StyletIoC; namespace StaffStuff.Services { public class ServicesModule : StyletIoCModule { protected override void Load() { Bind<IStaffData>().To<StaffData>().InSingletonScope(); } } }

屏幕

屏幕为派生自它的视图模型提供了验证、属性更改通知和活动状态监控。它还包括一个Parent属性,使视图模型能够知道哪个Conductor正在管理它,并允许它请求导体关闭它或激活不同的视图模型。在示例项目StaffViewModel中,它是由ShellViewModel激活的第一个视图模型,派生自Screen类。

using StaffStuff.Common.Interfaces; using StaffStuff.Common.Models; using Stylet; using System.Collections.Generic; namespace StaffStuff.ViewModels { public class StaffViewModel : Screen { private readonly IStaffData _staffData; public StaffViewModel(IStaffData staffData) { _staffData = staffData; } private List<Employee> _employees; public List<Employee> Employees { get => _employees; set => SetAndNotify(ref _employees, value); } protected override void OnInitialActivate() { Employees = _staffData.GetEmployees(); } public void StaffDetails(Employee employee) { var staffDetailsVM = new StaffDetailsViewModel { Employee = employee }; ((ShellViewModel)this.Parent).ActivateItem(staffDetailsVM); } } }

OnInitialActivate()方法仅在视图模型首次激活时调用一次。StaffViewModel的StaffDetails()方法调用导体的ActivateItem()方法,请求它激活StaffDetailsViewModel,触发导航到其关联的视图。

using StaffStuff.Common.Models; using Stylet; namespace StaffStuff.ViewModels { public class StaffDetailsViewModel : Screen { public StaffDetailsViewModel() { } private Employee _employee; public Employee Employee { get => _employee; set => SetAndNotify(ref _employee, value); } public void GoBack() { this.RequestClose(); } } }

GoBack()调用屏幕的RequestClose()方法,这反过来导致导体关闭视图模型。这触发导航回到上一个视图,通过重新激活导体的上一个活动项——由于导体是Conductor<T>.StackNavigation类型,也可以通过调用导体的GoBack()方法触发导航回到上一个活动项。

public void GoBack() { ((ShellViewModel)this.Parent).GoBack(); }

动作

可能已经注意到StaffViewModel和StaffDetailsViewModel都没有ICommand属性。这是因为Stylet消除了这样的属性,而是允许视图模型中的一个方法被设置为Button的Command属性的值。Stylet通过Actions实现这一点。

<Button Command="{s:Action GoBack}"> ... </Button> <DataTemplate x:Key="StaffDataTemplate" DataType="{x:Type models:Employee}"> <Border Margin="10" Cursor="Hand" BorderThickness="1" Background="#FF16394F" BorderBrush="#FF3F5666" CornerRadius="8" Width="200" Height="240"> ... <behaviors:Interaction.Triggers> <behaviors:EventTrigger EventName="MouseLeftButtonUp"> <behaviors:InvokeCommandAction Command="{s:Action StaffDetails}" CommandParameter="{Binding}" /> </behaviors:EventTrigger> </behaviors:Interaction.Triggers> </Border> </DataTemplate>

注意,也可以将CommandParameter的值传递给方法,如果它需要传递参数。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485