AI编程+CSFrameworkV6旗舰版Winform开发技能:csframework_winform_skills.md
AI编程+CSFrameworkV6旗舰版Winform开发技能:csframework_winform_skills.md
本文以真实 ERP 项目基于CSFrameworkV6旗舰版(.NET 8 WinForms + DevExpress 22.1 + EF Core + SQL Server)开发的实践为基础,反向提炼出一套可直接落地的 C/S 桌面应用开发标准,覆盖分层架构、命名、Designer.cs 职责、数据绑定、查询窗体、网格开发、事务落库与代码评审清单,可作为团队开发规范、代码评审依据与新人培训教材。
开发框架版本:https://www.csframework.com/cs-framework-6.0.htm (CSFrameworkV6.1旗舰版)
目录
一、技术栈与分层架构
1、三层职责边界
C/S 桌面应用建议固定 UI 窗体 → 业务层(bll)→ 数据层(dal) 的单向调用链,各层职责不得越界:
| 层 | 命名约定 | 职责边界 |
|---|---|---|
| UI 窗体 | frm_业务码 | 取界面输入、调业务层、展示结果;禁止写 SQL、禁止直连数据库 |
| 业务层 | bll_业务码 | 业务编排、读系统参数、调用数据层;不得被数据层反向引用(防循环依赖) |
| 数据层 | dal_业务码 | 全部 SQL/LINQ 落库、事务、业务校验(抛业务异常)、写数据日志 |
2、实体与目录组织
实体按用途分三类:tb_ 前缀(ORM 映射物理表)、req_ 前缀(查询参数类)、res_ 前缀(非 ORM 投影/展示类,不加 Table 特性、不写建表脚本)。窗体文件(.cs + .Designer.cs + .resx)成对存放在所属模块项目内,按业务模块划分程序集,而不是自造 Forms/Helper/Model 目录。
二、命名规范
1、类型命名
| 类型 | 前缀 | 示例 |
|---|---|---|
| 业务单据/查询窗体 | frm_ | frm_IN_Material(采购入库单)、frm_SettleLogQuery |
| 模态弹窗 | frmDialog_ | frmDialog_SelectDO |
| 数据层 / 业务层 | dal_ / bll_ | dal_PO / bll_PO |
| ORM 实体 | tb_ / dt_ / sys_ | tb_PO、dt_Product、sys_系统表 |
| 查询参数 / 投影类 | req_ / res_ | req_PurchaseHistoryQuery、res_tb_PO |
| 报表 | rpt_ | rpt_FA.frx |
2、控件命名
| 控件 | 约定命名 | 说明 |
|---|---|---|
| GridControl / GridView | gc / gv + 语义 | 汇总页 gcSummary/gvSummary,明细页 gcDetail/gvDetail,弹窗 gcList/gvList |
| 网格列 | col + 字段名 | colCustomerCode、colSupplierName |
| 查询条件输入 | txt_ 前缀 | txt_QueryInfo(快速搜索)、txt_DocDateFrom/To |
| 按钮 | btn + 动作 | btnQuery(查询)、btnEmpty(清空)、btnOk/btnCancel |
| 窗体标题 | lblFormTitle | 业务术语以此为准,避免以讹传讹 |
禁止 button1/textBox1 这类默认名;控件 Name 在属性面板修改,由设计器同步到 Designer.cs。窗体中文本术语以 lblFormTitle.Text、菜单 Text、报表为准——项目实测中曾出现记忆与实际标题不一致(如把"付款申请单"记成"费用申请单"),动手前先核对。
三、窗体基类体系
业务窗体必须继承框架基类,把日志、权限、按钮状态、数据加载等公共能力收敛到基类,禁止自建 BaseForm/MsgHelper 轮子:
| 基类 | 适用场景 | 关键能力 |
|---|---|---|
| frmBaseChild | 查询界面(上条件区 + 下网格) | AssertSearch 查询条件校验 |
| frmBaseDataForm | 基础资料/数据字典维护 | 汇总页/明细页页签、导航条 |
| frmBaseDialog | 模态弹窗 | 内容区 + 确定/取消按钮区 |
| frmBaseBusinessForm | 业务单据窗体 | 查看/编辑状态机、按钮状态门控 |
生命周期分工:构造函数第一行 InitializeComponent(),之后只做轻量初始化(禁止查库);Form_Load 只做网格布局注册、字典绑定、快捷键绑定,查询窗体默认不自动查数据,由【查询】按钮触发。
四、Designer.cs 职责与常见坑
固定 UI(控件、布局、列、事件)固化在 Designer.cs;主 .cs 只写业务逻辑。Designer.cs 内禁止 if/循环/SQL/业务逻辑/耗时操作,禁止在 InitializeComponent 里 new GridColumn。
两个高频坑(均来自真实踩坑):
① 编辑页标题栏加按钮 = Designer 4 处改动,缺一处不显示或编译不过:
1、字段声明;2、InitializeComponent 里 new 实例;3、pnlFormTitle.Controls.Add(...);4、属性块(FlatStyle、Cursor、尺寸、Location、Text、Click)。往已有按钮行插入时,必须连带重排其后按钮的 X 坐标,并确认面板宽度够用。
② 网格加列必须固化到 Designer(五件套),不要运行时动态加列:
1、字段声明;2、new GridColumn 实例化;3、列配置块(Caption/FieldName/AllowEdit/Visible/VisibleIndex/Width);4、Columns.AddRange 数组插入;5、原 VisibleIndex ≥ 插入点的列全部顺延 +N。改完用脚本提取全部 VisibleIndex 与 0..N-1 连续序列比对终检,防止列重叠错乱。
另外注意:多数模块未开隐式 using,Designer 文件里引用工具类要写全限定名;图标走统一的 images 目录加载(如 Globals.LoadImage("32_Refresh.png")),不要为加图标去改 .resx 的 base64。
五、数据绑定规范
实测很多框架项目并不使用 BindingSource 作为中间层,而是封装了按控件名反射绑定的机制——控件命名 txt+字段名 后,一行代码完成整面板绑定:
//编辑面板整批绑定:按控件名 txt+字段名 反射绑定(框架封装)
CommonTools.DoBindingEditorPanel(pcDetailEditor, currentEntity, "txt");
//单控件绑定(绑 EditValue)
DataBinder.BindingTextEdit(txt_Customer, entity, "CustomerName");
//代码赋值并触发 EditValueChanged 事件
txt_Customer.SetBindingValue(value, true);
//网格数据源:直接赋值投影类 List
gcDetail.DataSource = list;
gvDetail.ClearFilters();
公共数据字典下拉用统一绑定方法(DisplayMember=名称字段、ValueMember=代码字段);多选场景用 CheckedComboBoxEdit,其 EditValue 本身就是分隔字符串,与 string 模型字段天然对齐。
六、查询窗体标准写法
查询条件区固定布局:快速搜索框(浅绿底提示可输入)+ 日期三段式(范围下拉/开始/结束 + 按范围查勾选)+ 查询/清空按钮(HotFlat 风格、手型光标、带图标)。两段必写逻辑:
private void btnQuery_Click(object sender, EventArgs e)
{
//性能优化:查询条件不能为空
if (!AssertSearch(gcFindGroup, chk_ByDate)) return;
try
{
frmWaitingEx.ShowMe(this);
var list = new bll_XXX().Query(GetQueryParams());
gcDetail.DataSource = list;
gvDetail.ClearFilters();
ColumnFieldTool.SetColumnFieldTitle(gvDetail, "物理表名"); //自动读数据字典取中文列名
}
finally { frmWaitingEx.HideMe(this); }
}
private void Form_Load(object sender, EventArgs e)
{
frmGridCustomize.RegisterGrid(gvDetail); //注册网格:还原用户自定义布局
DevStyle.SetGridControlLayout(gcDetail, false);
txt_QueryInfo.HandleEnterKeyButton(btnQuery); //回车触发查询
txt_DateRange.BindingDateRange(txt_DocDateFrom, txt_DocDateTo); //日期快捷区间(本日/本周/本月)
}
private void btnEmpty_Click(object sender, EventArgs e)
{
CommonTools.ClearContainerEditorText(gcFindGroup); //一键清空条件区
}
数字列加合计(SummaryItemType.Sum);个别计算列在自动取中文列名之后手动覆盖 Caption。
七、网格(Grid)开发规范
① 布局持久化:统一用 frmGridCustomize.RegisterGrid(gv) 注册网格,框架负责保存/还原用户调整后的列宽、列位置。锁列必须双保险——Designer 静态设置一次,运行时在 RegisterGrid 之后再设一次(RegisterGrid 会还原布局覆盖设置):
private void Form_Load(object sender, EventArgs e)
{
frmGridCustomize.RegisterGrid(gvDetail); //还原用户布局
LockColumns(); //★ 锁列放在 RegisterGrid 之后(双保险)
}
private void LockColumns()
{
colCustomerCode.OptionsColumn.AllowEdit = false;
colCustomerCode.OptionsColumn.ReadOnly = true;
}
② 勾选多选:用 gv.GetCheckedRows<T>() 取勾选行,依赖 OptionsSelection.MultiSelect 且需 AllowGridMultiSelect(true) 显示勾选框列;未勾选时兜底取当前焦点行。
③ 右键菜单:菜单项常量集中定义(GridMenuNames 静态类),AddMenuItem 统一挂接图标与处理函数,在 DoExecuteCmd 的 switch 里分发——禁止在事件里散落写死。
④ 防闪烁:大批量刷新成对调用 BeginUpdate()/EndUpdate()(try/finally 保证成对)。
八、业务落库范式(下推生成/事务/日志)
"源单下推生成目标单据"是 ERP 高频场景,统一按以下 DAL 模板:校验、落库同一事务;异常抛 CustomException 回滚,不留半截数据;单号必须在事务内取号;数据日志写系统库且在事务提交后写:
public tb_Xxx GenerateBySource(List<string> listSourceNos) //★ List 支持批量
{
var nos = (listSourceNos ?? new List<string>())
.Where(w => !string.IsNullOrWhiteSpace(w)).Distinct().ToList();
if (nos.Count == 0) throw new CustomException("请选择要下推的源单据!");
try
{
_Database.BeginTransaction();
//1、取源单:存在性、审核标志(FlagApp=='Y')、业务规则校验,全部抛 CustomException
//2、防重复:按源单类型+号码查目标明细是否已生成
// ★ 单据类型必须用 nameof(DocTypeNames.X) 取"代码",常量值是中文名,写错编译不报错
//3、事务内取单据号
var no = DocNoHelper.GetDocNo(_Database, "单据码", _Loginer.Account);
_Database.Add<tb_Xxx>(master);
_Database.Add<tb_XxxDetail>(details);
_Database.CommitTransaction();
_Log.Log(DataLogType.add, "生成单据", nameof(tb_Xxx), no, _Loginer.Account, new { nos });
return master;
}
catch (Exception ex)
{
_Database.RollbackTransaction();
_Log.LogException(ex);
throw;
}
}
配套的"弹窗选择列表"范式:窗体继承 frmBaseDialog、构造器私有、对外只暴露静态 Execute(参数, out message) 方法——在弹窗确定事件里直接调 DAL 写库(成功才 DialogResult=OK),返回值带回提示信息;候选数据用 res_ 投影类承载。
★ nameof 铁律:单据类型常量类(如 DocTypeNames)的常量名是单据代码、常量值是中文显示名。判断数据库里的 SourceDocType/DocType 字段必须写 nameof(DocTypeNames.X)(得到 "X"),直接引用常量会得到中文,条件永远不成立且编译不报错、静默失效——这类 Bug 极难排查,代码评审必须盯住。
九、消息、异常与等待
禁止零散 MessageBox.Show,统一走框架封装:
| 场景 | 统一写法 |
|---|---|
| 业务校验失败(DAL/BLL) | throw new CustomException("…"); |
| UI 承接业务异常 | catch (CustomException ex) { frmMessageInfo.ShowWarning(ex.Message); } |
| UI 承接未知异常 | catch (Exception ex) { Msg.ShowException(ex); }(不把堆栈抛给用户) |
| 确认框 | if (Msg.AskQuestion("确定要生成吗?")) { … } |
| 耗时操作 | frmWaitingEx.ShowMe(this) / HideMe(this)(try/finally) |
| 数据日志 | 写系统库日志表;事务提交后写;原值在赋值前采集 |
十、金额精度与算术铁律
金额计算是财务类系统的重灾区,全项目必须统一口径:
| 项 | 口径 |
|---|---|
| 明细金额 | 统一 2 位小数 |
| 单价 / 数量 | 6 位 / 3 位(成本计算链 4~6 位单独保持) |
| 税额 | 差额法:税额 = ROUND(含税金额,2) − ROUND(不含税金额,2),保证价税合计恒等式 |
| 本币金额 | ROUND(原币金额 × 汇率, 2);本币单价由本币金额反推 |
| 舍入 | Math.Round 必须显式 MidpointRounding.AwayFromZero(默认银行家舍入会差 1 分钱) |
| 类型转换 | 服务端算术禁用有歧义重载的 ToDecimal()/ToDouble() 扩展,用 Convert.ToDecimal/ToDouble |
decimal amount = Math.Round(qty * price, 2, MidpointRounding.AwayFromZero);
十一、代码评审检查清单
每张业务窗体提交评审时逐项检查:
1、分层正确:UI 无 SQL;校验在 DAL/BLL 并抛 CustomException;数据层未反向引用业务层。
2、窗体继承了正确的框架基类,未自造 BaseForm/MsgHelper 轮子。
3、控件命名符合规范(gc/gv 语义名、txt_ 查询条件、col 列名),无默认控件名。
4、Designer 改动完整:标题栏按钮 4 处、加列五件套 + VisibleIndex 连续性终检通过。
5、Designer 无业务逻辑;图标走 images 目录统一加载,未动 .resx。
6、判断单据类型一律 nameof(常量),无直接引用常量值。
7、取号在事务内;校验/落库同一事务,异常回滚;数据日志事务提交后写系统库。
8、提示统一框架封装;等待统一等待窗体;无零散 MessageBox、无空 catch。
9、网格已注册布局;锁列双保险;勾选用 GetCheckedRows 且开了 MultiSelect;新列已固化 Designer。
10、金额 2 位小数、税额差额法、Math.Round 带 AwayFromZero、Convert 显式转换。
11、模型加字段先出 SQL 脚本(只增不删、幂等、放统一脚本目录),部署顺序=先脚本再更新客户端。
这套标准的价值在于"全部来自真实项目的踩坑复盘"——每一条规则背后都有一次返工或一次线上问题作为依据。建议团队以此为骨架,结合自身项目持续修订,并通过代码评审清单把它变成肌肉记忆。
了解更多 CSFrameworkV6 快速开发框架,请访问 C/S框架网。
CSFrameworkV6旗舰版 | 多语言及国际化|C/S开发框架
CSFrameworkV6旗舰版 | 导入导出Excel文件数据开发教程|C/S开发框架
CSFrameworkV6旗舰版:获取主菜单、模块菜单、用户有权限的菜单|C/S开发框架
CSFrameworkV6旗舰版 | MySql数据库笔记|C/S开发框架
C/S快速开发框架旗舰版CSFrameworkV6.0 | VS开发环境配置|C/S开发框架
CSFrameworkV6旗舰版 | 迭代开发 | 当前导航菜单、模块按钮与活动窗体同步|C/S开发框架
CSFrameworkV6旗舰版新增功能菜单权限设置|C/S开发框架
CSFrameworkV6旗舰版 | 基础资料窗体改装主从子表(多级明细表)解决方案|C/S开发框架
CSFrameworkV6旗舰版 | 主从子表(多级明细表)设计方案|C/S开发框架
CSFrameworkV6旗舰版 | 表格多选操作删除记录|C/S开发框架
CSFrameworkV6旗舰版|动态加载菜单Demo|C/S开发框架
CSFrameworkV6旗舰版 | 送货单报表C#源码参考|C/S开发框架
CSFrameworkV6旗舰版 | 单表基础资料窗体完整C#源码|C/S开发框架
CSFrameworkV6旗舰版|成功案例|塑料板材行业ERP系统|C/S开发框架
CSFrameworkV6旗舰版|成功案例|纸箱行业ERP系统|C/S开发框架
CSFrameworkV6.0旗舰版开发框架升级更新日志|C/S开发框架
什么是C/S结构? | C/S框架核心组成部分 | C/S框架-WebService部署图 | C/S框架-权限管理 | C/S结构系统框架 - 5.1旗舰版介绍 | C/S结构系统框架 - 功能介绍 | C/S结构系统框架 - 产品列表 | C/S结构系统框架 - 应用展示(图) | 三层体系架构详解 | C/S架构轻量级快速开发框架 | C/S框架网客户案例 | WebApi快速开发框架 | C/S框架代码生成器 | 用户授权注册软件系统 | 版本自动升级软件 | 数据库底层应用框架 | CSFramework.CMS内容管理系统 |
在线客服
