• 【WPF on .NET Core 3.0】 Stylet演示项目


    .NET Core 3.0已经发布了,除了一大堆令人激动的功能以外,也增加了对WPF的正式支持, 那么WPF在.NET Core 3.0下的开发体验如何呢?

    本文利用了Stylet框架开发.NET Core 3.0上的WPF应用程序.关于Stylet框架, 可能大家比较陌生, 它是一个轻量级(但是非常优秀!)的WPF框架, 最近也更新了对.NET Core 3.0的支持, 关于Stylet的介绍可以浏览我之前的一篇博文: ViewModel从未如此清爽 - 轻量级WPF MVVM框架Stylet.本文也包含了使用MVVM和Stylet的一些最佳实践.

    为了简单起见, 示例项目主要演示了使用Stylet开发WPF应用程序中常用的功能,如绑定,窗口弹出,表单验证等.没有使用数据库,也不包含如用户认证管理等一般的业务功能.

    项目全部代码都利用.NET的最新技术栈完成, 包括:

    • VS2019
    • .NET Core 3.0
    • C# 8

    文中也会对其中的一些新特性做出说明.

    下面就来跟随我体会一下这个船新的版本吧!

    前期准备

    • VS2019 16.3.2

    • .NET Core SDK 3.0.100

    • 安装stylet模板

      打开cmd或powershell运行以下命令(确保.NET Core 3.0的SDK已安装):

      dotnet new -i Stylet.Templates

    • 创建一个Stylet工程

      dotnet new stylet -o StyletBookStore

      这样一个使用Stylet的WPF应用程序就创建好了!

    VS2019打开StyletBookStore.csproj, 直接按F5运行:

    OK. 感觉不错!

    增加登录功能

    虽然是个演示项目,但是我们还是需要登录界面的:), Stylet中一个界面至少需要两部分: "ViewModel"和"View". 我们来分别实现它们.

    ViewModel

    在Pages文件夹下创建一个名为LoginViewModel.cs的类,用来实现登录的界面逻辑.并输入以下代码:

    public class LoginViewModel : Screen
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; } 
    
        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; }
    
        /// <summary>
        /// 登录
        /// </summary>
        public void Login()
        {
            // 在这实现登录逻辑
        }
    
        /// <summary>
        /// 登录的防护属性
        /// </summary>
        public bool CanLogin => !string.IsNullOrEmpty(UserName) &&
                                !string.IsNullOrEmpty(Password);
    }
    
    • 该类继承了Stylet.Screen, 这是Stylet中一个常用的ViewModel基类.

    • 定义了两个public属性, 分别代表用户名和密码.

    • 定义了一个名为Login的方法,用来实现登录的逻辑.我们会在稍后实现它.

    • 定义了一个名为CanLogin的防护属性, 用来检查Login方法是否可以运行. 只有当用户名和密码都输入时,才允许运行登录方法.

      防护属性(Guard Properties)是Stylet的一个功能, 是一个返回布尔型的只读属性. 属性名的命名约定为Can + 防护的方法名. 更多信息请浏览Guard Properties.

    启用可空引用类型

    下面我们试用一下C#8中新增加的特性: 可空引用类型.

    使用可空引用类型可在编译时就检查潜在的空引用问题.关于可空引用类型,请参考我之前翻译的一篇文章初试C# 8.0中"可空的引用类型"章节.

    右键点击StyletBookStore工程,选择"Edit Project File":

    在打开的StyletBookStore.csproj文件中增加一行配置:

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
    
    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>netcoreapp3.0</TargetFramework>
        <RootNamespace>StyletBookStore</RootNamespace>
        <UseWPF>true</UseWPF>
        <Nullable>enable</Nullable>  <!-- 启用可空引用类型 -->
    </PropertyGroup>
    
    ...
    
    </Project>
    

    在启用可空引用类型后,再次编译工程会出现两个警告:

    警告我们UserNamePassword未初始化. 可能在之后的使用中出现空引用异常.解决办法有两个:

    1. 为属性设置初始值

      public string UserName { get; set; } = String.Empty;
      public string Password { get; set; } = String.Empty;
      

      为用户名和密码设置初始值: 空字符串

    2. 将属性声明为可空类型

      public string? UserName { get; set; }
      public string? Password { get; set; }
      

      使用?标识用户名和密码是可空类型

    这里我们使用第1种方法,将用户名和密码初始化为空字符串.再次编译,警告就消失了.

    View

    在Pages文件夹下创建一个为名LoginView.xaml的文件,用来定义登录的UI. 其中主要的代码如下:

    <Window 
        ...
        d:DataContext="{d:DesignInstance pages:LoginViewModel}" 
        >
    <Grid>
        ...
        <TextBlock Grid.Row="0" Grid.Column="0" Text="用户名" Margin="5" VerticalAlignment="Center" HorizontalAlignment="Right"></TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="密码" Margin="5" VerticalAlignment="Center" HorizontalAlignment="Right"></TextBlock>
        <TextBox Grid.Row="0" Grid.Column="1" Margin="5" Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <PasswordBox Grid.Row="1" Grid.Column="1" Margin="5" wpf:PasswordHelper.Attach="True" wpf:PasswordHelper.Password="{Binding Path=Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></PasswordBox>
        <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="登录" Margin="5" Padding="15 5 15 5" Command="{s:Action Login}"></Button>
    </Grid>
    </Window>
    
    • 使用d:DataContext="{d:DesignInstance ...}"为XAML设置设计时的数据源, 以在编写XAML时获得智能提示.
    • 出于安全性考虑, WPF中密码框不支持绑定.这里为了简单起见,使用了一个扩展的附加属性类PasswordHelper,用来实现Password的绑定.具体代码请浏览PasswordHelper
    • 使用{s:Action Login}将登录按钮的Command绑定到Login方法中, 这样按下登录按钮就会执行ViewModel中的Login方法. 这是Stylet提供的功能, 名为Action. 更多信息请浏览Actions and Methods

    HotReload

    VS2019为XAML增加了一个便利功能HotReload, 该功能可允许开发者在应用程序运行时修改XAML代码,并立即看到修改后的效果:

    这样我们在设计UI时会方便很多:)

    实现登录逻辑

    修改Login方法,实现登录功能:

            /// <summary>
            /// 登录
            /// </summary>
            public void Login()
            {
                // 设计的非常健壮的用户验证机制:)
                if (UserName != "waku" || Password != "123")
                {
                    _windowManager.ShowMessageBox("用户名或密码不正确", "登录失败", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return;
                }
    
                RequestClose(true);
            }
    
    • 当用户输入的用户名和密码不符合我们非常健壮的验证规则时,弹出错误信息.

      这里使用了WindowManager.ShowMessageBox来显示消息对话框, WindowManager是Stylet提供的功能, 专门用来管理窗口的显示.

      为了使用WindowManager, 在ViewModel中声明一个IWindowManager接口类型的成员变量,并使用Stylet内置的IoC功能将它的实例通过构造方法注入进去.

          private readonly IWindowManager _windowManager;
          public LoginViewModel(IWindowManager windowManager)
          {
              _windowManager = windowManager;
          }
      

      这样就可在ViewModel中使用WindowManager了

      也许你更习惯使用MessageBox.Show来显示消息对话框, 但这么做你的ViewModel就和UI组件耦合在一起了,而这是违反MVVM设计模式的. 想象一下你需要为你的ViewModel编写测试代码时, 因为其中使用了MessageBox.Show, 那么在测试中处理与UI组件的交互就变成了很困难的事. 而使用Stylet中的WindowManager,你可以在测试中Mock它的接口IWindowManager来模拟消息框的行为.

    • 验证通过后,我们使用RequestClose(true)通知Stylet关闭Login窗口,并返回结果true.

    至此我们的登录ViewModel就完成了, 完整的代码如下:

    using System;
    using System.Windows;
    using Stylet;
    
    namespace StyletBookStore.Pages
    {
        public class LoginViewModel : Screen
        {
            private readonly IWindowManager _windowManager;
    
            /// <summary>
            /// 用户名
            /// </summary>
            public string UserName { get; set; } = String.Empty;
    
            /// <summary>
            /// 密码
            /// </summary>
            public string Password { get; set; } = String.Empty;
    
            public LoginViewModel(IWindowManager windowManager)
            {
                _windowManager = windowManager;
            }
    
            /// <summary>
            /// 登录
            /// </summary>
            public void Login()
            {
                // 设计的非常安全的用户验证机制:)
                if (UserName != "waku" || Password != "123")
                {
                    _windowManager.ShowMessageBox("用户名或密码不正确", "登录失败", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return;
                }
    
                RequestClose(true);
            }
    
            /// <summary>
            /// 登录的防护属性
            /// </summary>
            public bool CanLogin => !string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password);
        }
    }
    

    此时工程结构应该是这样的:

    显示登录窗口

    我们已经准备好登录窗口了, 但是如何显示它呢? 还记得Stylet为我们准备好的ShellViewShellViewModel吗? 它们就是负责程序的主窗口的,就从它们入手.

    按以往的经验, 可能在ShellView.xaml.cs中写下这样的代码:

        public ShellView()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        }
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var window = new LoginView();
            window.ShowDialog();
        }
    

    实际上这也是违反MVVM的, 因为我们直接操作了视图, 正确的做法是以ViewModel为中心, 由ViewModel来驱动各视图的显示.

    实际上你可以直接从工程中删除所有的.xaml.cs文件, Stylet不需要它们!

    我们来看看使用Stylet该如何实现.

    ShellViewModel类中增加OnViewLoaded的重写方法:

        protected override void OnViewLoaded()
        {
            var loginViewModel = _container.Get<LoginViewModel>();
            var result = _windowManager.ShowDialog(loginViewModel);
            if (result != true)
            {
                RequestClose();
            }
        }
    
    • OnViewLoaded方法是基类Screen中定义的一个方法, 当ViewModel绑定的View加载完成后会调用该方法.
    • 使用Container.Get方法获取LoginViewModel的实例.Container是Stylet中IoC的容器, 通过它的Get方法我们可以取得所有注册到IoC中类的实例.
    • 然后调用WindowManager.ShowDialog, 并将LoginViewModel实例做为参数传递进去.Stylet就会为我们显示子窗口了.
    • 当登录窗口关闭后, 判断窗口的返回值如果不是true, 代表登录失败. 这里和登录一样,我们仍然调用RequestClose进行关闭操作, 但是因为Shell是主窗口, 该窗口关闭后整个应用程序也随之退出了.

    为了使用ContainerWindowManager, 我们同样在构造方法中将其注入,并使用类成员变量来接收它们:

        private readonly IContainer _container;
        private readonly IWindowManager _windowManager;
    
        public ShellViewModel(IContainer container, IWindowManager windowManager)
        {
            _container = container;
            _windowManager = windowManager;
        }
    

    这与在登录中注入WindowManager基本类似, 就不再解释了.

    注入Container并使用Get方法, 被称为"服务定位模式"(Service Locator Pattern), 对于它是否也违反了MVVM有很多争议. Stylet的WIKI上对此也有讨论和解决办法. 但是我个人很喜欢这种方式.

    完整的ShellViewModel类代码如下:

    using Stylet;
    using StyletIoC;
    
    namespace StyletBookStore.Pages
    {
        public class ShellViewModel : Screen
        {
            private readonly IContainer _container;
            private readonly IWindowManager _windowManager;
    
            public ShellViewModel(IContainer container, IWindowManager windowManager)
            {
                _container = container;
                _windowManager = windowManager;
            }
    
            protected override void OnViewLoaded()
            {
                var loginViewModel = _container.Get<LoginViewModel>();
                var result = _windowManager.ShowDialog(loginViewModel);
                if (result != true)
                {
                    RequestClose();
                }
            }
        }
    }
    

    运行

    好了, 我们的第一个功能 - "登录"就已经完成了, 编译运行应用程序:

    我们需要确认以下4个功能点:

    • 当"用户名"或"密码"为空时, 是不允许登录的("登录"按钮处于禁用状态).
    • 用户名输入"waku", 并且密码输入"123", 登录窗口关闭, 回到主窗口.
    • 否则显示"用户名或密码不正确"的消息框.
    • 点击登录窗口右上角的"X"按钮,整个应用程序退出.

    需要确认的功能点很少, 所以我们手动确认也可很快完成, 但是对于复杂一些的应用程序, 手动确认就很麻烦了, 而且在频繁的迭代过程中, 回归测试也很必要. 所以下一篇文章中我们会学习如何为ViewModel编写单元测试代码.

    本篇到此为止, 希望朋友们能多多留言. 源码托管在GITHUB上.

    Happy Coding~

  • 相关阅读:
    官场22条潜规则,职场谁说不是呢
    pomelo使用中的常见问题
    马斯诺需求金字塔
    Mac上使用brew安装nvm来支持多版本的Nodejs
    Redis 集群解决方案 Codis
    Linux下压缩某个文件夹(文件夹打包)
    使用forever运行nodejs应用
    nodejs npm常用命令
    Mac安装Brew
    C#2.0 迭代器
  • 原文地址:https://www.cnblogs.com/waku/p/11692434.html
Copyright © 2020-2023  润新知