在前面随笔介绍的《ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理》里面,介绍了如何改进和完善审计日志和登录日志的应用服务端和Winform客户端,由于篇幅限制,没有进一步详细介绍Winform界面的开发过程,本篇随笔介绍这部分内容,并进一步扩展Winform界面的各种情况处理,力求让它进入一个新的开发里程碑。

1、回顾审计日志和登陆日志管理界面

前面介绍了如何扩展审计日志应用服务层(Application Service层)和ApiCaller层(API客户端调用封装层),同时也展示审计日志和登录日志在Winform界面的展示,由于整个ABP框架目前我还是采用了.net core的开发路线,所有的封装项目都是基于.net core基础上进行的。不过由于目前Winform还没有能够以 .net core进行开发,所以界面端还是用.net framework的方式开发,不过可以调用 .net standard的类库。

下面是审计日志的列表展示界面,和我之前的Winform框架一样的布局,因此我重用了Winform框架里面公用类库项目、基础界面封装项目、分页控件等内容,因此整个界面看起来还是很一致的。

由于审计日志主要供底层记录,因此在界面不能增加增删改的操作,我们只需要分页查询,和导出记录即可,如下窗体界面所示。

而明细内容,可以通过双击或者右键选择菜单打开即可弹出新的展示界面,主要展示审计日志里面的各项信息。

而对于用户登录日志来说,处理方式差不多,也是通过在列表中查询展示,并在列表中整合右键菜单或者双击处理,可以查看登录明细内容。

通过双击或者右键选择菜单打开即可弹出新的展示界面,主要展示登录日志里面的各项信息。

2、Winform界面代码实现

上面展示了列表界面和查看明细界面,实际上我们Winform的界面内部是如何处理的呢,我们这里对其中的一些关键处理进行分析介绍。

列表界面的窗体初始化代码如下所示

    /// <summary>/// 审计日志/// </summary>    public partial class FrmAuditLog : BaseDock{private const string Id_FieldName = "Id";//Id的字段名称public FrmAuditLog(){InitializeComponent();//分页控件初始化事件this.winGridViewPager1.OnPageChanged += new EventHandler(winGridViewPager1_OnPageChanged);this.winGridViewPager1.OnStartExport += new EventHandler(winGridViewPager1_OnStartExport);this.winGridViewPager1.OnEditSelected += new EventHandler(winGridViewPager1_OnEditSelected);this.winGridViewPager1.OnAddNew += new EventHandler(winGridViewPager1_OnAddNew);this.winGridViewPager1.OnDeleteSelected += new EventHandler(winGridViewPager1_OnDeleteSelected);this.winGridViewPager1.OnRefresh += new EventHandler(winGridViewPager1_OnRefresh);this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;this.winGridViewPager1.ShowLineNumber = true;this.winGridViewPager1.BestFitColumnWith = false;//是否设置为自动调整宽度,false为不设置this.winGridViewPager1.gridView1.DataSourceChanged +=new EventHandler(gridView1_DataSourceChanged);this.winGridViewPager1.gridView1.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);this.winGridViewPager1.gridView1.RowCellStyle += new DevExpress.XtraGrid.Views.Grid.RowCellStyleEventHandler(gridView1_RowCellStyle);//关联回车键进行查询foreach (Control control in this.layoutControl1.Controls){control.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);}//屏蔽某些处理this.winGridViewPager1.ShowAddMenu = false;this.winGridViewPager1.ShowDeleteMenu = false;}

这些是使用分页控件来初始化一些界面的处理事件,不要一看就抱怨需要编写这么多代码,这些基本上都是代码生成工具生成的,后面会介绍。

其实窗体的加载的时候,主要逻辑是初始化字典列表和展示列表数据,如下代码所示。

        /// <summary>/// 编写初始化窗体的实现,可以用于刷新/// </summary>public override async void  FormOnLoad(){   await InitDictItem();await BindData();}

其中这里都是使用async和await 配对实现的异步处理操作。我们对于审计日志列表来说,字典模块没有需要字典绑定信息,那么默认为空不用修改。

        /// <summary>/// 初始化字典列表内容/// </summary>private async Task InitDictItem(){//初始化代码//await this.txtCategory.BindDictItems("报销类型");await Task.FromResult(0);}

那么我们主要处理的就是BindData的数据绑定操作了。

        /// <summary>/// 绑定列表数据/// </summary>private async Task BindData(){this.winGridViewPager1.DisplayColumns = "Id,BrowserInfo,ClientIpAddress,ClientName,CreationTime,Result,UserId,UserNameOrEmailAddress";this.winGridViewPager1.ColumnNameAlias = await UserLoginAttemptApiCaller.Instance.GetColumnNameAlias();//字段列显示名称转义//获取分页数据列表var result = await GetData();//设置所有记录数和列表数据源this.winGridViewPager1.PagerInfo.RecordCount = result.TotalCount; //需先于DataSource的赋值,更新分页信息this.winGridViewPager1.DataSource = result.Items;this.winGridViewPager1.PrintTitle = "用户登录日志报表";}

其中我们通过 调用服务端接口 GetColumnNameAlias 来获取对应的别名,其实我们也可以在Winform客户端设置对等的别名处理,如下代码所示。

            #region 添加别名解析//this.winGridViewPager1.AddColumnAlias("Id", "Id");//this.winGridViewPager1.AddColumnAlias("BrowserInfo", "浏览器");//this.winGridViewPager1.AddColumnAlias("ClientIpAddress", "IP地址");//this.winGridViewPager1.AddColumnAlias("ClientName", "客户端");//this.winGridViewPager1.AddColumnAlias("CreationTime", "时间");//this.winGridViewPager1.AddColumnAlias("Result", "结果");//this.winGridViewPager1.AddColumnAlias("UserId", "用户ID");//this.winGridViewPager1.AddColumnAlias("UserNameOrEmailAddress", "用户名或邮件");#endregion

只是基于服务端更加方便,也减少客户端的编码了。

而获取数据主要通过 GetData 函数进行统一获取对应的列表和数据记录信息,如下是GetData的函数实现。

        /// <summary>/// 获取数据/// </summary>/// <returns></returns>private async Task<IPagedResult<UserLoginAttemptDto>> GetData(){//构建分页的条件和查询条件var pagerDto = new UserLoginAttemptPagedDto(this.winGridViewPager1.PagerInfo){UserNameOrEmailAddress = this.txtUserNameOrEmailAddress.Text.Trim(),};//日期和数值范围定义//时间,需在UserLoginAttemptPagedDto中添加DateTime?类型字段CreationTimeStart和CreationTimeEndvar CreationTime = new TimeRange(this.txtCreationTime1.Text, this.txtCreationTime2.Text); //日期类型pagerDto.CreationTimeStart = CreationTime.Start;pagerDto.CreationTimeEnd = CreationTime.End;var result = await UserLoginAttemptApiCaller.Instance.GetAll(pagerDto);return result;}

这个函数里面,主要是接收列表界面里面的查询条件,并构建对应的分页查询条件,这样根据条件DTO就可以请求服务器的数据了。

前面讲了,这个过滤条件并返回对应的数据,主要就是在Application Service层,设置CreateFilteredQuery的控制逻辑即可,如下所示。

        /// <summary>/// 自定义条件处理/// </summary>/// <param name="input">分页查询Dto对象</param>/// <returns></returns>protected override IQueryable<AuditLog> CreateFilteredQuery(AuditLogPagedDto input){//构建关联查询Queryvar query = from auditLog in Repository.GetAll()join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoinfrom joinedUser in userJoin.DefaultIfEmpty()where auditLog.UserId.HasValueselect new AuditLogAndUser { AuditLog = auditLog, User = joinedUser };//过滤分页条件return query.WhereIf(!string.IsNullOrEmpty(input.UserName), t => t.User.UserName.Contains(input.UserName)).WhereIf(input.ExecutionTimeStart.HasValue, s => s.AuditLog.ExecutionTime >= input.ExecutionTimeStart.Value).WhereIf(input.ExecutionTimeEnd.HasValue, s => s.AuditLog.ExecutionTime <= input.ExecutionTimeEnd.Value).Select(s => s.AuditLog);}

这里就不在赘述服务层的逻辑代码,主要关注我们本篇的主题,Winform的界面实现逻辑。

上面通过GetData获取到服务端数据后,我们就可以把列表数据绑定到分页控件上面,让分页控件调用GridControl 进行展示出来即可。

            //设置所有记录数和列表数据源this.winGridViewPager1.PagerInfo.RecordCount = result.TotalCount;this.winGridViewPager1.DataSource = result.Items;

数据的导出操作,我们这里也顺便提一下,虽然这些代码是基于代码生成工具生成的,不过还是提一下逻辑处理。

数据的导出操作,主要就是通过GetData获取到数据后,转换为DataTable,并通过Apose.Cell进行写入Excel文件即可,如下代码所示。

        /// <summary>/// 导出的操作/// </summary>        private async void ExportData(){string file = FileDialogHelper.SaveExcel(string.Format("{0}.xls", moduleName));if (!string.IsNullOrEmpty(file)){//获取分页数据列表var result = await GetData();var list = result.Items;DataTable dtNew = DataTableHelper.CreateTable("序号|int,Id,时间,用户名,服务,操作,参数,持续时间,IP地址,客户端,浏览器,自定义数据,异常,返回值");DataRow dr;int j = 1;for (int i = 0; i < list.Count; i++){dr = dtNew.NewRow();dr["序号"] = j++;dr["Id"] = list[i].Id;dr["浏览器"] = list[i].BrowserInfo;dr["IP地址"] = list[i].ClientIpAddress;dr["客户端"] = list[i].ClientName;dr["自定义数据"] = list[i].CustomData;dr["异常"] = list[i].Exception;dr["持续时间"] = list[i].ExecutionDuration;dr["时间"] = list[i].ExecutionTime;dr["操作"] = list[i].MethodName;dr["参数"] = list[i].Parameters;dr["服务"] = list[i].ServiceName;dr["用户名"] = list[i].UserName;dr["返回值"] = list[i].ReturnValue;dtNew.Rows.Add(dr);}try{string error = "";AsposeExcelTools.DataTableToExcel2(dtNew, file, out error);if (!string.IsNullOrEmpty(error)){MessageDxUtil.ShowError(string.Format("导出Excel出现错误:{0}", error));}else{if (MessageDxUtil.ShowYesNoAndTips("导出成功,是否打开文件?") == System.Windows.Forms.DialogResult.Yes){System.Diagnostics.Process.Start(file);}}}catch (Exception ex){LogTextHelper.Error(ex);MessageDxUtil.ShowError(ex.Message);}}            }

而对于编辑或者查看界面,如下所示。

它的实现逻辑主要就是获取单个记录,然后在界面上逐一绑定控件内容显示即可。

        /// <summary>/// 数据显示的函数/// </summary>public async override void DisplayData(){InitDictItem();//数据字典加载(公用)if (!string.IsNullOrEmpty(ID)){#region 显示信息var info = await AuditLogApiCaller.Instance.Get(ID.ToInt64());if (info != null){tempInfo = info;//重新给临时对象赋值,使之指向存在的记录对象
txtBrowserInfo.Text = info.BrowserInfo;txtClientIpAddress.Text = info.ClientIpAddress;txtClientName.Text = info.ClientName;txtCustomData.Text = info.CustomData;txtException.Text = info.Exception;txtExecutionDuration.Value = info.ExecutionDuration;txtExecutionTime.SetDateTime(info.ExecutionTime);txtMethodName.Text = info.MethodName;txtParameters.Text = ConvertJson(info.Parameters);txtServiceName.Text = info.ServiceName;if (info.UserId.HasValue){txtUserId.Value = info.UserId.Value;}txtUserName.Text = info.UserName;//转义的用户名
}#endregion }else{}this.btnAdd.Visible = false;this.btnOK.Visible = false;}

当然对于新增或编辑的界面,我们需要处理它的保存或者更新的操作事件,虽然审计日志不需要这些操作,不过生成的编辑窗体界面,依旧保留这些处理逻辑,如下代码所示。

        /// <summary>/// 新增状态下的数据保存/// </summary>/// <returns></returns>public async override Task<bool> SaveAddNew(){AuditLogDto info = tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用
            SetInfo(info);try{#region 新增数据tempInfo = await AuditLogApiCaller.Instance.Create(info);if (tempInfo != null){//可添加其他关联操作return true;}#endregion}catch (Exception ex){LogTextHelper.Error(ex);MessageDxUtil.ShowError(ex.Message);}return false;}/// <summary>/// 编辑状态下的数据保存/// </summary>/// <returns></returns>public async override Task<bool> SaveUpdated(){AuditLogDto info = await AuditLogApiCaller.Instance.Get(ID.ToInt64());if (info != null){SetInfo(info);try{#region 更新数据tempInfo = await AuditLogApiCaller.Instance.Update(info);if (tempInfo != null){//可添加其他关联操作return true;}#endregion}catch (Exception ex){LogTextHelper.Error(ex);MessageDxUtil.ShowError(ex.Message);}}return false;}

我们可以根据实际的需要,对我们业务对象的窗体进行一定的改造即可。

3、复杂一点的WInform界面处理

例如对于前面的列表界面,一个比较复杂一点的列表展示内容,需要在查询条件中绑定字典列表,并对列表记录的一些状态进行特殊展示等,以及需要考虑增加、导入、导出等功能按钮,这些默认的列表生成界面就有的。

如下是对于产品信息的一个界面展示,也是基于ABP框架构建的服务进行数据展示的例子。

和前面介绍的例子一样,也是基于分页控件进行展示的,我们来看看状态的处理吧。

由于状态和用户信息,我们在数据库里面记录的是整形的数据信息,也就是状态为0,1的这样,以及用户ID等,我们如果需要转义给客户端使用,那么我们需要在对应的DTO里面增加一些字段进行承载,如下所示是产品信息的DTO对象,除了本身CreateProductDto必须有的字段外,我们另外增加了两个属性,如下代码所示。

然后我们在应用服务接口的ConvertDto转义函数里面增加自己的处理转义逻辑即可,如下代码所示。

        /// <summary>/// 对记录进行转义/// </summary>/// <param name="item">dto数据对象</param>/// <returns></returns>protected override void ConvertDto(ProductDto item){//如需要转义,则进行重写#region 参考代码//用户名称转义if (item.CreatorUserId.HasValue){//需在ProductDto中增加CreatorUserName属性item.CreatorUserName = _userRepository.Get(item.CreatorUserId.Value).UserName;}if (item.Status.HasValue){item.StatusDisplay = item.Status.Value == 0 ? "正常" : "停用";}#endregion}

这样客户端就可以采用这两个属性展示信息了。

前面也介绍了,对于产品类型属性,我们一般是一个字典信息的,因此我们可以集成绑定字典的处理,如下代码所示。

这个BindDictItems是扩展函数,通过扩展函数,我们对控件类型的绑定字典操作进行处理即可,具体的逻辑代码如下所示。

    /// <summary>/// 扩展函数封装/// </summary>internal static class ExtensionMethod{/// <summary>/// 绑定下拉列表控件为指定的数据字典列表/// </summary>/// <param name="control">下拉列表控件</param>/// <param name="dictTypeName">数据字典类型名称</param>/// <param name="emptyFlag">是否添加空行</param>public static async Task BindDictItems(this ComboBoxEdit control, string dictTypeName, bool isCache = true, bool emptyFlag = true){await BindDictItems(control, dictTypeName, null, isCache, emptyFlag);}/// <summary>/// 绑定下拉列表控件为指定的数据字典列表/// </summary>/// <param name="control">下拉列表控件</param>/// <param name="dictTypeName">数据字典类型名称</param>/// <param name="defaultValue">控件默认值</param>/// <param name="emptyFlag">是否添加空行</param>public static async Task BindDictItems(this ComboBoxEdit control, string dictTypeName, string defaultValue, bool isCache = true, bool emptyFlag = true){var dict = await DictItemUtil.GetDictByDictType(dictTypeName, isCache);List<CListItem> itemList = new List<CListItem>();foreach (string key in dict.Keys){itemList.Add(new CListItem(key, dict[key]));}control.BindDictItems(itemList, defaultValue, emptyFlag);}......

最后我们可以看到,字典列表的效果如下所示。

新增产品信息界面如下所示。

4、基于代码工具的Winform界面快速生成

这些都是标准的Winform界面模板,因此可以利用代码生成工具进行快速开发,利用代码生成工具Database2Sharp快速生成来实现ABP优化框架类文件的生成,以及界面代码的生成,然后进行一定的调整就是本项目的代码了。

ABP框架的基础代码生成我们就不再这里介绍了,主要介绍下Winform展示界面和编辑界面的快速生成即可。

在生成Abp框架的Winform界面面板中,配置我们查询条件、列表展示、编辑展示内容等信息后,就可以生成对应的界面,然后复制到项目中使用即可,整个过程是比较快速的,这些开发便利可是花了我很多反复核对和优化NVelocity模板的开发时间的。

如下是代码生成工具Database2Sharp关于ABP框架的Winform界面配置。

设置好后直接生成,代码工具就可以依照模板来生成所需要的WInform列表界面和编辑界面的内容了,如下是生成的界面代码。

放到VS项目里面,就看到对应的窗体界面效果了。

生成界面后,进行一定的布局调整就可以实际用于生产环境了,省却了很多时间。

转载于:https://www.cnblogs.com/wuhuacong/p/11018187.html

ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程相关推荐

  1. ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理

    在前面两篇随笔<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>和<ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程>开始 ...

  2. ABP开发框架前后端开发系列——框架的总体介绍

    ABP开发框架前后端开发系列--框架的总体介绍 ABP是ASP.NET Boilerplate的简称,ABP是一个开源且文档友好的应用程序框架. ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领 ...

  3. ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理

    我们了解ABP框架内部自动记录审计日志和登录日志的,但是这些信息只是在相关的内部接口里面进行记录,并没有一个管理界面供我们了解,但是其系统数据库记录了这些数据信息,我们可以为它们设计一个查看和导出这些 ...

  4. winform前后端框架_ABP开发框架前后端开发系列(1)框架的总体介绍

    ABP是ASP.NET Boilerplate的简称,ABP是一个开源且文档友好的应用程序框架.ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型.学习使用ABP ...

  5. 这没啥挑的,全新java前后端开发需掌握的框架及技术

    一.Java开发 1.J2EE架构及主流框架,spring4.spring boot.spring MVC.spring Security.spring cloud.struct2.hibernate ...

  6. 萌新一手包App前后端开发日记(一)

    从事Android移动端也有些日子了,还记得一开始选择这份工作,是憧憬着有朝一日能让亲朋好友用上自己开发的软件,但日子久了才发现,并不是所有的公司,所有的项目的适用群体都是"亲朋好友&quo ...

  7. 3分钟搞懂前后端开发的区别

    上周末见了好多开发的年轻朋友,问了我一个问题:"前后端的区别和要求是什么?"分不清前后端开发的区别和要求,一种是因为前后端都了解,号称"全栈工程师",但又什么都 ...

  8. .net core webapi 前后端开发分离后的配置和部署

    背景:现在越来越多的企业都采用了在开发上前后端分离,前后端开发上的分离有很多种,那么今天,我来分享一下项目中得的前后端分离. B/S  Saas 项目:(这个项目可以理解成个人中心,当然不止这么点功能 ...

  9. uniapp 如何给搜索框设值_uni-app搜索功能前后端开发(页面)

    uni-app搜索功能前后端开发(页面) 博客说明文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 借助的插件地址 展示 前端是使用vue, ...

最新文章

  1. TC配置文件WCMD.INI详解,只能在ini重修改的配置
  2. python 3d绘图平面_python 用 matplotlib 在 3D 空间中绘制平面 实例详解
  3. Kingbase和PostgreSQL中如何查看当前连接的进程信息
  4. 十二天深入理解计算机系统(一)
  5. Spring AOP 源码系列(一)解析 AOP 配置信息
  6. 程序清单3-1 测试能否对标准输入设置偏移量
  7. [置顶] android 自定义圆角ImageView以及锯齿的处理
  8. sql server 2000能否得到一个表的最后更新日期?
  9. perl的文件操作(1)
  10. 计算机主机房净高,机房建设标准
  11. 鲁棒控制--simulink不确定模型仿真
  12. 华为2019校招实习笔试-软件题
  13. 计算机复试考研专业课,2021考研计算机复试常考专业课内容
  14. L1-7 机工士姆斯塔迪奥 (20 分),C语言
  15. 研究生复试------12 学做菜
  16. linux漏洞分析,Spring-data-commons(CVE-2018-1273)漏洞分析
  17. MySQL笔记--2、3、4、5
  18. 四轴码垛机器人MDH模型运动学
  19. Vue搜索框显示最近(历史)搜索记录
  20. C++文件流fstream相关操作

热门文章

  1. 微软Surface Pro笔记本如何设置u盘启动教程
  2. 如何安装 OneNote for Windows 10 的离线安装包
  3. mysql数据推荐算法_Java+Mysql实现简单在线电影、音乐、图书推荐系统 基于用户的协同过滤推荐算法实现 源代码下载...
  4. Win10 Version 1803 四月更新正式版 ISO 镜像下载
  5. 华南理工计算机应用在线答题,华南理工大学计算机应用基础随堂练习题目及答案...
  6. 基于JAVA汽车出租平台计算机毕业设计源码+数据库+lw文档+系统+部署
  7. 淘宝的字体也改变了(今天)
  8. 《动手学深度学习》笔记---3.16
  9. html空间坐标系,世界坐标空间与观察坐标系之间的转换
  10. ROS2机器人坐标工具→tf2静态广播←Python