本着每天记录一点成长一点的原则, 打算将目前完成的一个 WPF 项目相关的技术分享出来, 供团队学习与总结.
总共分三个部分:
基础篇主要针对 C# 初学者, 巩固 C# 常用知识点;
中级篇主要针对 WPF 布局与 MaterialDesign 美化设计, 在减轻代码量的情况做出漂亮的应用;
终极篇为框架应用实战, 包含系统分层, MVVM 框架 Prism 安装与使用, ORM 框架 EntityFramework Core 配置与使用, 开源数据库 PostgreSQL 配置与使用.
目录
Prism+MaterialDesign+EntityFramework Core+PostgreSQL WPF 开发总结 之 基础篇
Prism+MaterialDesign+EntityFramework Core+PostgreSQL WPF 开发总结 之 中级篇
前言
此篇主要介绍系统分层模型, 如何安装 Prism 快速开发模板与 MVVM 框架使用, 如何配置 ORM 框架 Entity Framework Core 与使用, 以及 PostgreSQL 数据库配置.
系统分层
项目比较简单, 大概分层模型如下:
View 双向绑定 ViewModel;
ViewModel 调用 Service 取得 DataModel 业务数据;
Service 通过调用 Repository 取得 Entity 数据;
Repository 调用 Entity Framework Core, 自动创建 Sql 执行并返回 Entity 对象;
Entity Framework Core 通过驱动链接数据库.
如果项目功能或者对接端末比较多, 最好扩展成微服务.
MVVM 框架之 Prism
MVVM(Model-view-viewmodel)是微软的 WPF 和 Silverlight 架构师之一 John Gossman 于 2005 年发布的软件架构模式. 目的就是把用户界面设计与业务逻辑开发分离, 方便团队开发和自动化测试. 目前流行的 Android 开发, Web 开发都在使用, 具体 MVVM 的介绍参照个人博客: 核心框架 MVVM 与 MVC,MVP 的区别(图文详解).
一, 无框架的 MVVM 实现
设计与逻辑分离的基本就是绑定, 通过发布者订阅者模式实现数据更新通知.
1, 属性绑定
默认属性为单向绑定, 如果需要双向绑定需要实现 INotifyPropertyChanged 接口.
第一步: 一般是建立如下基类.
- using System;
- using System.ComponentModel;
- using System.Runtime.CompilerServices;
- namespace MvvmDemo.Common
- {
- /// <summary>
- /// Viewmodel 基类, 属性双向绑定基础
- /// </summary>
- public class ViewModelBase : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- /// <summary>
- /// 属性变更通知
- /// </summary>
- /// <param name="propertyName">属性名</param>
- public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
- {
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
- }
- }
- }
- View Code
第二步: 各个 ViewModel 继承基类.
- public class UserViewModel : ViewModelBase
- {
- private string _userId;
- private string _userName;
- /// <summary>
- /// 用户名
- /// </summary>
- public string UserId
- {
- get
- {
- return _userId;
- }
- set
- {
- _userId = value;
- NotifyPropertyChanged();
- }
- }
- /// <summary>
- /// 用户名
- /// </summary>
- public string UserName
- {
- get
- {
- return _userName;
- }
- set
- {
- _userName = value;
- NotifyPropertyChanged();
- }
- }
- }
- View Code
第三步: Xaml 绑定属性, 实现消息通知.
- <TextBox Text="{Binding UserID,Mode=TwoWay}" />
- <TextBox Grid.Row="1" Text="{Binding UserName,Mode=OneWay}" />
- View Code
备注: 通过 IValueConverter 可以做一些特殊绑定处理. 比如, 经典的就是 Bool 值控制 Visibility.
- [ValueConversion(typeof(bool), typeof(Visibility))]
- public class BoolToVisibiltyConverter : MarkupExtension, IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- bool flag = false;
- if (value is bool)
- {
- flag = (bool)value;
- }
- else if (value is bool?)
- {
- bool? nullable = (bool?)value;
- flag = nullable.HasValue ? nullable.Value : false;
- }
- return (flag ? Visibility.Visible : Visibility.Collapsed);
- }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return value;
- }
- public override object ProvideValue(IServiceProvider serviceProvider)
- {
- return this;
- }
- }
- View Code
Xaml 绑定: 头部需要引入命名空间.
xmlns:converter="clr-namespace:WpfMvvm.Core.Converters"
- <Button
- Grid.Row="2"
- Visibility="{Binding ShowFlg,Converter={converter:BoolToVisibiltyConverter}}"
- Command="{Binding AddCmd}"
- Content="登录" />
- View Code
2, 事件绑定
WPF 提供了 Command 事件处理属性, 想利用控件中的 Command 属性需要实现了 ICommand 接口的属性.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows.Input;
- namespace MvvmDemo.Common
- {
- public class DelegateCommand<T>: ICommand
- {
- /// <summary>
- /// 命令
- /// </summary>
- private Action<T> _Command;
- /// <summary>
- /// 命令可否执行判断
- /// </summary>
- private Func<T, bool> _CanExecute;
- /// <summary>
- /// 可执行判断结束后通知命令执行
- /// </summary>
- public event EventHandler CanExecuteChanged;
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="command">命令</param>
- public DelegateCommand(Action<T> command):this(command,null)
- {
- }
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="command">命令</param>
- /// <param name="canexecute">命令可执行判断</param>
- public DelegateCommand(Action<T> command,Func<T,bool> canexecute)
- {
- if(command==null)
- {
- throw new ArgumentException("command");
- }
- _Command = command;
- _CanExecute = canexecute;
- }
- /// <summary>
- /// 命令执行判断
- /// </summary>
- /// <param name="parameter">判断数据</param>
- /// <returns > 判定结果(True: 可执行, False: 不可执行)</returns>
- public bool CanExecute(object parameter)
- {
- return _CanExecute == null ? true : _CanExecute((T)parameter);
- }
- /// <summary>
- /// 执行命令
- /// </summary>
- /// <param name="parameter">参数</param>
- public void Execute(object parameter)
- {
- _Command((T)parameter);
- }
- }
- }
- View Code
使用它作为事件属性的类型就可以了.
- /// <summary>
- /// 登陆命令
- /// </summary>
- public DelegateCommand<string> LoginCommand => new DelegateCommand<string>(
- s =>
- {
- // todo
- },
- s => !string.IsNullOrEmpty(s)
- );
- View Code
二, Prism 的 MVVM 实现
至于 Prism 有很多种理由让我选择它, 比如:
支持 MVVM(Binding,Notification,Command 等), 微软成员维护
支持 Unity 和 DryIoc 两种 IoC 容器
支持 WPF,UWP,Xamarin.Froms 开发
封装界面跳转
封装弹出消息框
自带项目模板与快速开发代码片段
创建 View 时自动创建 ViewModel
默认自动绑定 ViewModel 到 View
... 等等
1, 配置 Prism
最简单的方法: 安装 Prism Template Pack 扩展包.
2, 使用 Prism
通过 Prism 项目模板创建项目, 目前可以创建 WPF(.Net Framework 和. Net Core),UWP,Xamarin.Froms 等应用.
以前支持四种容器, 现在只支持两种 IoC 容器: Unity,DryIoc.
* 备注: 如果通过 Prism 模板创建项目时出现以下错误:
这是因为 Autofac 已经不被支持. 解决办法: regedit 进入注册表 HKEY_CURRENT_USER\Software\Prism, 把 SelectedContainer 删除或者改成 Unity.
生成的解决方案如下:
亮点: 解决方案中自动设置了 ViewModel 的 IoC 配置, MainWindow.xaml 中 ViewModel 的绑定也自动设置了.
下面通过建立一个简单的局部界面跳转实例, 体验一把 Prism 的高效率: cmd,propp,vs 智能提示.
Prism 包提供的代码片段如下, 要好好加以利用:
此次项目还用到了以下特性:
2.1 Region Navigation
局部页面跳转:
传递对象参数;
跳转前确认;
自定义如何处理已经显示过的页面(覆盖与否);
通过 IRegionNavigationJournal 接口可以操作页面跳转履历(返回与前进等).
如上例所示简单应用.
第一步: 标识显示位置.
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
第二步: 在 App.xaml.cs 注册跳转页面.
- public partial class App
- {
- protected override Windows CreateShell()
- {
- return Container.Resolve<MainWindow>();
- }
- protected override void RegisterTypes(IContainerRegistry containerRegistry)
- {
- containerRegistry.RegisterForNavigation<PageTwo, PageTwoViewModel>();
- }
- }
- View Code
第三步: 使用 IRegionManager 实现跳转.
- // 指定需要显示的页面名字与显示位置的 ContentControl 的名字
- _manager.RequestNavigate("ContentRegion", "PageTwo");
- 2.2,Modules
如果系统功能比较多最好进行分块处理, 如下面订单和用户信息的分块处理.
App.xaml.cs 中统一各个模块数据.
- // ModuleLoader 会把各个模块的 IoC 依赖注入数据汇总共有管理
- protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
- {
- moduleCatalog.AddModule<OrderModule>();
- moduleCatalog.AddModule<CustomerModule>();
- }
- View Code
各个 Module 里面还是一样, 使用到的所有 Service 和 Repository 都注册, 使用 IoC 容器进行生命周期管理.
- public class OrderModule : IModule
- {
- public void OnInitialized(IContainerProvider containerProvider)
- {
- }
- public void RegisterTypes(IContainerRegistry containerRegistry)
- {
- containerRegistry.RegisterForNavigation<MainWin>(PageDefine.Order);
- containerRegistry.Register<ISearchService, SearchService>();
- }
- }
- View Code
- 2.3,Dialog Service
自定义消息弹出框, 比如警告, 错误, 提示等消息框.
第一步: 自定义消息框控件, ViewModel 继承 IDialogAware 接口并实现:
- public class NotificationDialogViewModel : BindableBase, IDialogAware
- {
- private DelegateCommand<string> _closeDialogCommand;
- public DelegateCommand<string> CloseDialogCommand =>
- _closeDialogCommand ?? (_closeDialogCommand = new DelegateCommand<string>(CloseDialog));
- private string _message;
- public string Message
- {
- get { return _message; }
- set { SetProperty(ref _message, value); }
- }
- private string _title = "Notification";
- public string Title
- {
- get { return _title; }
- set { SetProperty(ref _title, value); }
- }
- public event Action<IDialogResult> RequestClose;
- protected virtual void CloseDialog(string parameter)
- {
- ButtonResult result = ButtonResult.None;
- if (parameter?.ToLower() == "true")
- result = ButtonResult.OK;
- else if (parameter?.ToLower() == "false")
- result = ButtonResult.Cancel;
- RaiseRequestClose(new DialogResult(result));
- }
- public virtual void RaiseRequestClose(IDialogResult dialogResult)
- {
- RequestClose?.Invoke(dialogResult);
- }
- public virtual bool CanCloseDialog()
- {
- return true;
- }
- public virtual void OnDialogClosed()
- {
- }
- public virtual void OnDialogOpened(IDialogParameters parameters)
- {
- Message = parameters.GetValue<string>("message");
- }
- }
- View Code
第二步: App.xaml.cs 中注册自定义的消息框, 从而覆盖默认的消息框:
- public partial class App
- {
- protected override Windows CreateShell()
- {
- return Container.Resolve<MainWindow>();
- }
- protected override void RegisterTypes(IContainerRegistry containerRegistry)
- {
- containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
- }
- }
- View Code
第三步: 通过 IDialogService 使用消息框:
- private void ShowDialog()
- {
- var message = "This is a message that should be shown in the dialog.";
- //using the dialog service as-is
- _dialogService.ShowDialog("NotificationDialog", new DialogParameters($"message={message}"), r =>
- {
- if (r.Result == ButtonResult.None)
- Title = "Result is None";
- else if (r.Result == ButtonResult.OK)
- Title = "Result is OK";
- else if (r.Result == ButtonResult.Cancel)
- Title = "Result is Cancel";
- else
- Title = "I Don't know what you did!?";
- });
- }
- View Code
第四步: 定义消息框显示属性:
- <prism:Dialog.WindowStyle>
- <Style TargetType="Window">
- <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
- <Setter Property="ResizeMode" Value="NoResize" />
- <Setter Property="ShowInTaskbar" Value="False" />
- <Setter Property="SizeToContent" Value="WidthAndHeight" />
- </Style>
- </prism:Dialog.WindowStyle>
- View Code
其他用法可以参照 Prism 开源库: https://github.com/PrismLibrary/Prism
Entity Framework Core + PostgreSQL
EntityFrameworkCore: 是对象关系映射 (ORM) 程序, 支持语言集成查询 Linq, 是轻量, 可扩展, 开源跨平台的数据访问框架. 下一个 5.0 版本将与. NET 5.0 一起发布. EntityFrameworkCore 只支持 CodeFirst,EntityFramework 支持 DB First 和 Code First. 之所以选择 EFCore 是因为:
支持 CodeFirst
支持 Linq
双向映射(linq 映射成 sql, 结果集映射成对象)
速度很快
PostgreSQL: 是开源先进的对象 - 关系型数据库管理系统(ORDBMS), 有些特性甚至连商业数据库都不具备. 支持 JSON 数据存储, 表之间还可以继承.
一, 配置 EFCore 与 PostgreSQL
※PostgreSQL 安装参照个人博客:[Windows] PostgreSQL 安装
1, 引入针对 PostgreSQL 的 EFCore 包
2, 添加 DB 操作上下文
数据库链接替换为你的链接, 一般都是放配置文件管理.
添加 Users 字段, 通过 EFCore 将自动创建 Users 表.
- using System;
- using Microsoft.EntityFrameworkCore;
- using WpfMccm.Entitys;
- namespace WpfMvvm.DataAccess
- {
- public class UserDbContext : DbContext
- {
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- optionsBuilder.UseNpgsql("Server=127.0.0.1;Database=HBMCS;Port=5432;User Id=test;Password=test;Ssl Mode=Prefer;",
- npgsqlOptionsAction: options =>
- {
- options.CommandTimeout(60);
- options.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorCodesToAdd: null);
- });
- }
- public DbSet<User> Users { get; set; }
- }
- }
- View Code
3, 安装 Microsoft.EntityFrameworkCore.Tools 工具
CodeFirst 必备神器. 进入程序包管理器控制台, 输入以下命名安装 EFCore 设计工具:
※必须安装在启动项目里面, 不然会失败.
Install-Package Microsoft.EntityFrameworkCore.Tools
4, 创建 Migration
程序包管理器控制台, 默认项目一定要选择 DB 操作上下文的项目, 然后执行命令: InitDB 是文件区分, 可以任意修改.
Add-Migration InitDB
执行成功之后, 生成带 InitDB 区分的表定义数据文件:
6, 生成数据库脚本(生产阶段用, 开发阶段可跳过)
程序包管理器控制台, 执行如下命令生成 SQL 脚本文件:
Script-Migration
- CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
- "MigrationId" character varying(150) NOT NULL,
- "ProductVersion" character varying(32) NOT NULL,
- CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
- );
- CREATE TABLE "Users" (
- "ID" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
- "Name" text NULL,
- "Age" integer NOT NULL,
- CONSTRAINT "PK_Users" PRIMARY KEY ("ID")
- );
- INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
- VALUES ('20200413133616_InitDB', '3.1.3');
- View Code
如果系统已经上线, 安全起见则需要使用这个方法生成 SQL 脚本, 手动执行 SQL 更新数据库.
7, 更新数据库(开发阶段用)
程序包管理器控制台, 执行如下命令将表定义更新到 DB(按文件名的时间顺顺添加):
Update-Database
这样我们就通过类创建了一个数据库表 Users, 同时默认会在__EFMigrationsHistory 履历表添加一条合并记录.
※如果__EFMigrationsHistory 中记录存在则忽略本次更新.
二, 使用 DB 上下文操作数据库
1, 创建 IRepository,DB 操作基本接口
- public interface IRepository<TEntity> where TEntity : class
- {
- Task<TEntity> GetAsync(int id);
- Task<bool> AddAsync(TEntity obj);
- }
- View Code
2, 创建 UserRepository,User 专用的 DB 操作类
- public class UserRepository : IRepository<User>
- {
- private readonly DbContext _dbContext;
- private readonly DbSet<User> _dbSet;
- public UserRepository(UserDbContext dbContext)
- {
- _dbContext = dbContext;
- _dbSet = dbContext.Set<User>();
- }
- public async Task<bool> AddAsync(User obj)
- {
- _dbSet.Add(obj);
- return await _dbContext.SaveChangesAsync()> 0;
- }
- public async Task<User> GetAsync(int id)
- {
- return await _dbSet.FindAsync(id);
- }
- }
- View Code
如果需要进行事务操作, 可以使用下面方法:
- var tran= _dbContext.Database.BeginTransaction();
- tran.Commit();
- View Code
3,Service 层调用 UserRepository 就可以完成用户的操作.
总结
此篇量稍微有点多, 非常感谢能看到这里. 整体来说 Prism 简化了应用的设计与架构, EFCore 简化了数据库操作.
来源: https://www.cnblogs.com/lixiaobin/p/wpfdevreport.html