zoukankan      html  css  js  c++  java
  • Prism完成的一个WPF项目

     本着每天记录一点成长一点的原则,打算将目前完成的一个WPF项目相关的技术分享出来,供团队学习与总结。

    总共分三个部分:

    基础篇主要针对C#初学者,巩固C#常用知识点;

    中级篇主要针对WPF布局与MaterialDesign美化设计,在减轻代码量的情况做出漂亮的应用;

    终极篇为框架应用实战,包含系统分层、MVVM框架Prism安装与使用、ORM框架EntityFramework Core配置与使用、开源数据库Postgresql配置与使用。

    目录

    1. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 基础篇
    2. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 中级篇
    3. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 终极篇

    前言

    此篇主要介绍系统分层模型、如何安装Prism快速开发模板与MVVM框架使用、如何配置ORM框架Entity Framework Core与使用、以及Postgresql数据库配置。

    系统分层

    项目比较简单,大概分层模型如下:

    1. View双向绑定ViewModel;
    2. ViewModel调用Service取得DataModel业务数据;
    3. Service通过调用Repository取得Entity数据;
    4. Repository调用Entity Framework Core,自动创建Sql执行并返回Entity对象;
    5. 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));
                }
            }
        }
    }
    复制代码

    第二步:各个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();
                }
            }
        }
    复制代码

    第三步:Xaml绑定属性,实现消息通知。

            <TextBox Text="{Binding UserID,Mode=TwoWay}" />
            <TextBox Grid.Row="1" Text="{Binding UserName,Mode=OneWay}" />

    备注:通过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;
            }
        }
    复制代码

    Xaml绑定:头部需要引入命名空间。

    xmlns:converter="clr-namespace:WpfMvvm.Core.Converters"
            <Button
                Grid.Row="2"
                Visibility="{Binding ShowFlg,Converter={converter:BoolToVisibiltyConverter}}"
                Command="{Binding AddCmd}"
                Content="登录" />

    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);
            }
        }
    }
    复制代码

    使用它作为事件属性的类型就可以了。

    复制代码
            /// <summary>
            /// 登陆命令
            /// </summary>
            public DelegateCommand<string> LoginCommand => new DelegateCommand<string>(
                            s =>
                            {
                                // todo
                            },
                            s => !string.IsNullOrEmpty(s)
                            );
    复制代码

    二、Prism的MVVM实现

    至于Prism有很多种理由让我选择它,比如:

    1. 支持MVVM(Binding、Notification、Command等)、微软成员维护
    2. 支持Unity和DryIoc两种IOC容器
    3. 支持WPF、UWP、Xamarin.Froms开发
    4. 封装界面跳转
    5. 封装弹出消息框
    6. 自带项目模板与快速开发代码片段
    7. 创建View时自动创建ViewModel
    8. 默认自动绑定ViewModel到View
    9. ...等等

    1、配置Prism

    最简单的方法:安装Prism Template Pack扩展包。

    2、使用Prism

    通过Prism项目模板创建项目,目前可以创建WPF(.Net Framework和.Net Core)、UWP、Xamarin.Froms等应用。

     以前支持四种容器,现在只支持两种IOC容器:Unity、DryIoc。

     *备注:如果通过Prism模板创建项目时出现以下错误:

    这是因为Autofac已经不被支持。解决办法:regedit进入注册表HKEY_CURRENT_USERSoftwarePrism,把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注册跳转页面。

     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>();
            }

    各个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>();
            }
        }
    复制代码

    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");
            }
        }
    复制代码

    第二步:App.xaml.cs中注册自定义的消息框,从而覆盖默认的消息框:

    复制代码
        public partial class App
        {
            protected override Window CreateShell()
            {
                return Container.Resolve<MainWindow>();
            }
    
            protected override void RegisterTypes(IContainerRegistry containerRegistry)
            {
                containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
            }
        }
    复制代码

    第三步:通过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!?";
                });
            }
    复制代码

    第四步:定义消息框显示属性:

    复制代码
        <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>
    复制代码

    其他用法可以参照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; }
        }
    }
    复制代码

    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');
    复制代码

    如果系统已经上线,安全起见则需要使用这个方法生成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);
        }

    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);
            }
        }
    复制代码

    如果需要进行事务操作,可以使用下面方法:

               var tran= _dbContext.Database.BeginTransaction();
               tran.Commit();

    3、Service层调用UserRepository就可以完成用户的操作。

    总结

    此篇量稍微有点多,非常感谢能看到这里。整体来说Prism简化了应用的设计与架构,EFCore简化了数据库操作。

    每天成就一小步,积累下来就是一大步。 转发本文请注明出处,谢谢您的阅读与分享!
     
  • 相关阅读:
    秒杀项目之——通过令牌发放机制,控制秒杀大闸以及队列泄洪
    Guava的使用
    秒杀项目之细节随笔记录————
    redis搭建集群
    redis内存满了怎么办?
    多个电脑上免密登陆命令、scp远程拷贝、修改文件的用户和组
    克隆完虚拟机后修改网卡,修改静态IP
    redis基本操作和 过期时间设置以及持久化方案
    linux操作系统安装运行Redis
    云服务器以及linux操作系统打开防火墙,在墙上开一个小口
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12732919.html
Copyright © 2011-2022 走看看