【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(1)
.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>
在启用可空引用类型后,再次编译工程会出现两个警告:
警告我们UserName
和Password
未初始化. 可能在之后的使用中出现空引用异常.解决办法有两个:
为属性设置初始值
public string UserName { get; set; } = String.Empty;
public string Password { get; set; } = String.Empty;
为用户名和密码设置初始值: 空字符串
将属性声明为可空类型
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为我们准备好的ShellView
和ShellViewModel
吗? 它们就是负责程序的主窗口的,就从它们入手.
按以往的经验, 可能在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是主窗口, 该窗口关闭后整个应用程序也随之退出了.
为了使用Container
和WindowManager
, 我们同样在构造方法中将其注入,并使用类成员变量来接收它们:
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)的更多相关文章
- 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(3) - 使用Conductor切换页面
前两章中, 我们已经实现了这个图书管理系统的登录窗口, 并实施了完善的单元测试. 该是时候回过头来关注我们的主窗口了. 一个功能丰富的系统一般会有多个页面, 我们图书管理系统虽然是"简易&q ...
- 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(2) - 单元测试
上一章中我们完成了一个简单的登录功能, 这一章主要演示如何对Stylet工程中的ViewModel进行单元测试. 回忆一下我们的登录逻辑,主要有以下4点: 当"用户名"或" ...
- 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(4) - 图书列表界面
在前三章中我们完成了登录窗口, 并掌握了使用Conductor来切换窗口, 但这些其实都是在为我们的系统打基础. 而本章中我们就要开始开发系统的核心功能, 即图书管理功能了. 通过本章, 我们会接触到 ...
- Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台
Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台 微软将WinForms和WPF带到.NET Core 3.0这一事实,相信大家都有所了解,这是否意味着它在Linux ...
- .Net Core .Net Core V1.0 创建MVC项目
.Net Core V1.0 创建MVC项目 创建MVC项目有两种方式: 一.创建Web项目:(有太多没用的东西要去删太麻烦) 2.项目目录结构: 此种方法要注意的是,会创建好多个json文件,下面就 ...
- 用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 第三 ...
- .Net Core 3.0开源可视化设计CMS内容管理系统建站系统
简介 ZKEACMS,又名纸壳CMS,是可视化编辑设计的内容管理系统.基于.Net Core开发可跨平台运行,并拥有卓越的性能. 纸壳CMS基于插件式设计,功能丰富,易于扩展,可快速创建网站. 布局设 ...
- .Net大局观(2).NET Core 2.0 特性介绍和使用指南
.NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,系统地介绍了.NET Core 2.0及生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路径.提纲来用. ...
- .Net Core 2.0 生态(2).NET Core 2.0 特性介绍和使用指南
.NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,介绍了.NET Core 2.0新特性.工具支持及系统生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路 ...
随机推荐
- requests + BeautifulSoup + json
requests: response.text 以 unicode 格式显示响应的文本 response.content 以 二进制 格式显示响应的文本 BeautiSoup: sou ...
- Android开发教程:开发框架基本原理
1.提供应用程序框架(Framework) 开发者可以遵照这些框架搭建应用程序读者可以结合J2SE平台的Applet框架或J2ME平台的移动信息设备套件框架来理解Android平台的应用程序框架. 每 ...
- 为git创建远程仓库
首先生成ssh公钥: 将公钥添加到git: 测试秘钥是否通过: 然后就可以到web界面看到标注的地方被绿了: 但是我的没有绿,不知道为啥,难道没有女朋友的原因吗? rm -rf .ssh 重来好几遍都 ...
- Yum未完成事务问题
1.安装 yum-complete-transaction [root@linux-node1 ~]# yum -y install yum-utils 2.清除yum缓存 [root@linux-n ...
- css3不定宽高水平垂直居中
1 justify-content:center;//子元素水平居中 2 align-items:center;//子元素垂直居中 3 display:-webkit-flex; 在父级元素上面加上上 ...
- 知名大厂如何搭建大数据平台&架构
今天我们来看一下淘宝.美团和滴滴的大数据平台,一方面进一步学习大厂大数据平台的架构,另一方面也学习大厂的工程师如何画架构图.通过大厂的这些架构图,你就会发现,不但这些知名大厂的大数据平台设计方案大同小 ...
- 使用Java元注解和反射实现简单MVC框架
Springmvc的核心是DispatcherServlet来进行各种请求的拦截,进而进行后续的各种转发处理.流程图如下: 说明:客户端发出一个http请求给web服务器,web服务器对http请求进 ...
- java字符串,数组,集合框架重点
1.字符串的字面量是否自动生成一个字符串的变量? String str1 = “abc”; Sring str2 = new String (“abc”); 对于str1:Jvm在遇到双 ...
- .net core 3.0 Signalr - 实现一个业务推送系统
## 介绍 ASP.NET Core SignalR 是一个开源代码库,它简化了向应用添加实时 Web 功能的过程. 实时 Web 功能使服务器端代码能够即时将内容推送到客户端. SignalR 的适 ...
- .net core中使用Bumblebee架设微服务网关
Bumblebee是款基于.net core开发开源的http服务网关,经过最近版本的完善在功能足以满足作为微服务网关的需要.在微服务网关功能中它提供了应用服务负载,故障迁移,安全控制,监控跟踪和日志 ...