.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~

【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(1)的更多相关文章

  1. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(3) - 使用Conductor切换页面

    前两章中, 我们已经实现了这个图书管理系统的登录窗口, 并实施了完善的单元测试. 该是时候回过头来关注我们的主窗口了. 一个功能丰富的系统一般会有多个页面, 我们图书管理系统虽然是"简易&q ...

  2. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(2) - 单元测试

    上一章中我们完成了一个简单的登录功能, 这一章主要演示如何对Stylet工程中的ViewModel进行单元测试. 回忆一下我们的登录逻辑,主要有以下4点: 当"用户名"或" ...

  3. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(4) - 图书列表界面

    在前三章中我们完成了登录窗口, 并掌握了使用Conductor来切换窗口, 但这些其实都是在为我们的系统打基础. 而本章中我们就要开始开发系统的核心功能, 即图书管理功能了. 通过本章, 我们会接触到 ...

  4. Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台

    Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台 微软将WinForms和WPF带到.NET Core 3.0这一事实,相信大家都有所了解,这是否意味着它在Linux ...

  5. .Net Core .Net Core V1.0 创建MVC项目

    .Net Core V1.0 创建MVC项目 创建MVC项目有两种方式: 一.创建Web项目:(有太多没用的东西要去删太麻烦) 2.项目目录结构: 此种方法要注意的是,会创建好多个json文件,下面就 ...

  6. 用VSCode开发一个asp.net core 2.0+angular 5项目(4): Angular5全局错误处理

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...

  7. .Net Core 3.0开源可视化设计CMS内容管理系统建站系统

    简介 ZKEACMS,又名纸壳CMS,是可视化编辑设计的内容管理系统.基于.Net Core开发可跨平台运行,并拥有卓越的性能. 纸壳CMS基于插件式设计,功能丰富,易于扩展,可快速创建网站. 布局设 ...

  8. .Net大局观(2).NET Core 2.0 特性介绍和使用指南

    .NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,系统地介绍了.NET Core 2.0及生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路径.提纲来用. ...

  9. .Net Core 2.0 生态(2).NET Core 2.0 特性介绍和使用指南

    .NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,介绍了.NET Core 2.0新特性.工具支持及系统生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路 ...

随机推荐

  1. requests + BeautifulSoup + json

    requests: response.text      以 unicode 格式显示响应的文本 response.content    以 二进制 格式显示响应的文本 BeautiSoup: sou ...

  2. Android开发教程:开发框架基本原理

    1.提供应用程序框架(Framework) 开发者可以遵照这些框架搭建应用程序读者可以结合J2SE平台的Applet框架或J2ME平台的移动信息设备套件框架来理解Android平台的应用程序框架. 每 ...

  3. 为git创建远程仓库

    首先生成ssh公钥: 将公钥添加到git: 测试秘钥是否通过: 然后就可以到web界面看到标注的地方被绿了: 但是我的没有绿,不知道为啥,难道没有女朋友的原因吗? rm -rf .ssh 重来好几遍都 ...

  4. Yum未完成事务问题

    1.安装 yum-complete-transaction [root@linux-node1 ~]# yum -y install yum-utils 2.清除yum缓存 [root@linux-n ...

  5. css3不定宽高水平垂直居中

    1 justify-content:center;//子元素水平居中 2 align-items:center;//子元素垂直居中 3 display:-webkit-flex; 在父级元素上面加上上 ...

  6. 知名大厂如何搭建大数据平台&架构

    今天我们来看一下淘宝.美团和滴滴的大数据平台,一方面进一步学习大厂大数据平台的架构,另一方面也学习大厂的工程师如何画架构图.通过大厂的这些架构图,你就会发现,不但这些知名大厂的大数据平台设计方案大同小 ...

  7. 使用Java元注解和反射实现简单MVC框架

    Springmvc的核心是DispatcherServlet来进行各种请求的拦截,进而进行后续的各种转发处理.流程图如下: 说明:客户端发出一个http请求给web服务器,web服务器对http请求进 ...

  8. java字符串,数组,集合框架重点

    1.字符串的字面量是否自动生成一个字符串的变量? String  str1  =  “abc”; Sring  str2  =   new String (“abc”); 对于str1:Jvm在遇到双 ...

  9. .net core 3.0 Signalr - 实现一个业务推送系统

    ## 介绍 ASP.NET Core SignalR 是一个开源代码库,它简化了向应用添加实时 Web 功能的过程. 实时 Web 功能使服务器端代码能够即时将内容推送到客户端. SignalR 的适 ...

  10. .net core中使用Bumblebee架设微服务网关

    Bumblebee是款基于.net core开发开源的http服务网关,经过最近版本的完善在功能足以满足作为微服务网关的需要.在微服务网关功能中它提供了应用服务负载,故障迁移,安全控制,监控跟踪和日志 ...