WinForms 参数界面封装(二)
2026/5/16 18:16:14 网站建设 项目流程

WinForms 参数界面封装(二)

上一篇我先讲了背景。

也就是为什么参数一多以后,手工拖控件会越来越慢,最后我开始把参数页面往封装和标准化上整理。

这一篇继续往下讲。

这次不再只说思路,而是结合我现在项目里的代码,讲一下这套参数框架到底是怎么拆的。

我现在的做法,核心不是“做一个万能页面”,而是把参数页拆成几层,让每层只做自己的事。

这样后面新增参数、调整分组、改权限、加新页面时,都会顺很多。


一、我现在这套参数框架,核心就是四层

如果用最直白的话说,我现在这套参数界面封装,主要分成四层:

  • 参数数据层
  • 参数描述层
  • 界面生成层
  • 配置读写层

看起来名字很多,但每层做的事情其实很清楚。


二、第一层:参数数据层,只负责“参数有哪些”

这一层就是参数类本身。

比如我现在项目里就有这些:

  • cla_Para
  • cla_Para_correct
  • cla_productme
  • cla_Para_handle
  • cla_Para_knob

这一层不负责界面,也不负责控件布局,它只负责一件事:

参数到底有哪些字段。

cla_Para里,就是一堆真实项目参数,比如:

[XmlRoot("cla_Para")] public class cla_Para { public int is_colseMES = 1; public int is_gray1st = 1; public int is_gray2st = 1; public int is_door = 1; public float jintaicurrent_min = 0; public float jintaicurrent_max = 0; public float workcurrent_min = 0; public float workcurrent_max = 0; public string boot = ""; public string software = ""; public string hardware = ""; }

这一层我现在尽量保持简单。

也就是说:

参数类里只放参数本身。

不把页签、分组、显示名、权限这些东西混进来,这样参数类会干净很多。


三、第二层:参数描述层,负责“参数怎么显示”

这一层是我觉得最关键的地方。

因为手工做参数页时,最乱的地方往往不是参数值本身,而是这些信息总散在窗体里:

  • 它放哪个页签
  • 它放哪个分组
  • 它显示什么名字
  • 它单位是什么
  • 它谁能改
  • 它是普通输入还是勾选框

所以我把这些东西单独抽成了一层参数描述。

核心类就是:

  • ParaItemMeta
  • ClaParaSchema
  • ParaLayoutOptions

1.ParaItemMeta:描述一个参数该怎么显示

这个类本质上就是“参数显示说明书”。

代码里大概长这样:

public class ParaItemMeta { public string FieldName { get; set; } = ""; public string TabName { get; set; } = "默认页"; public string GroupName { get; set; } = "默认组"; public string DisplayName { get; set; } = ""; public string Unit { get; set; } = ""; public int Order { get; set; } = 0; public ParaLayoutKind LayoutKind { get; set; } = ParaLayoutKind.Pair; public decimal Min { get; set; } = -999999M; public decimal Max { get; set; } = 9999999M; public int Decimals { get; set; } = 2; public decimal Increment { get; set; } = 1M; public bool Visible { get; set; } = true; public bool ReadOnly { get; set; } = false; public int WriteLevel { get; set; } = 3; }

这一层出来以后,一个参数就不只是“字段名”。

它还带着:

  • 页签
  • 分组
  • 显示名称
  • 单位
  • 顺序
  • 布局方式
  • 最小值、最大值、小数位
  • 写入权限

这样后面界面生成时,就不用再靠窗体代码到处写判断了。

2.ClaParaSchema:把整页参数描述组织起来

如果说ParaItemMeta描述的是“一个参数”,那ClaParaSchema描述的就是“整个参数页”。

我现在是用它来集中配置:

  • 哪些参数出现
  • 每个参数放哪
  • 每个参数怎么显示

比如代码里已经是这种写法:

list.Add(Pair(nameof(cla_Para.R_min), TAB_P1, G_STATIC, "电阻下限", order += 10, unit: "Ω", writeLevel: 1)); list.Add(Pair(nameof(cla_Para.R_max), TAB_P1, G_STATIC, "电阻上限", order += 10, unit: "Ω", writeLevel: 1)); list.Add(Full(nameof(cla_Para.boot), TAB_P1, G_BANBEN, "BOOT号", order += 10, editorWidth: 360, writeLevel: 2)); list.Add(Check(nameof(cla_Para.a), TAB_SYS, G_SYS, "测试布尔参数", order += 10, writeLevel: 0));

我现在比较喜欢这种方式。

因为以后如果要改:

  • 显示名
  • 分组
  • 页签
  • 单位
  • 权限

基本都集中在 schema 里处理,不需要再去 Designer 里一点点翻控件。

3.ParaLayoutOptions:控制整体布局规则

参数页除了“参数怎么显示”,还有一个问题:

页面怎么排版。

比如:

  • 默认一行放几组
  • 标签宽度多少
  • 编辑框宽度多少
  • 单位宽度多少
  • 某个分组要不要特殊处理

所以我单独做了ParaLayoutOptions

大概是这样:

public class ParaLayoutOptions { public int DefaultPairCountPerRow { get; set; } = 4; public int LabelWidth { get; set; } = 180; public int EditorWidth { get; set; } = 150; public int UnitWidth { get; set; } = 50; public int RowGap { get; set; } = 6; public int GroupPadding { get; set; } = 10; public List<ParaGroupLayout> GroupLayouts { get; } = new List<ParaGroupLayout>(); }

这一层的价值是:

布局规则也从窗体里抽出来了。

以后改排版,不需要直接改一堆控件位置,而是改布局配置。


四、第三层:界面生成层,负责“把描述变成界面”

前两层做好以后,才轮到真正的界面生成。

这一层的核心类就是:

DynamicParaEditor<T>

这个类是整套参数封装里最像“发动机”的部分。

它做的事情其实很明确:

  • 读取参数类字段
  • 读取 schema 描述
  • 自动生成页签
  • 自动生成分组
  • 自动生成控件
  • 建立字段和控件映射
  • 支持对象加载和回写

主入口大概是这样:

public void Build(TabControl tabControl) { if (tabControl == null) return; tabControl.TabPages.Clear(); _editorMap.Clear(); List<ParaItemMeta> visibleItems = _schema .Where(x => x.Visible) .ToList(); List<string> tabNames = visibleItems .Select(x => x.TabName) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (string tabName in tabNames) { // 创建页签、分组并生成控件 } }

也就是说,我现在新建一个参数页,不再是先手工拖控件,而是:

  1. 定义参数类
  2. 写 schema
  3. 写布局规则
  4. 调用Build()自动生成

这样做最直接的好处就是:

把大量重复做参数页的时间省下来了。


五、这一层里,我最看重的不是“自动生成”,而是“统一入口”

很多人一看到这种写法,第一反应是:

“哦,就是自动生成界面。”

但我自己现在更看重的是另一点:

界面生成终于有统一入口了。

以前每个参数页都自己写一套,问题很多:

  • 命名风格不一致
  • 分组方式不一致
  • 控件读写方式不一致
  • 权限判断到处都是
  • 后期维护特别累

现在统一走DynamicParaEditor<T>以后,这些动作开始变成标准动作了。

这其实比“自动生成”本身更值钱。


六、第四层:配置读写层,负责“参数怎么保存和恢复”

参数页如果只是显示出来,还不够。

后面还要解决这些问题:

  • 参数怎么保存
  • 参数怎么读取
  • 文件损坏怎么办
  • 并发读写怎么办
  • 默认配置怎么恢复

所以我把配置读写也单独做了一层:

XmlConfigStore<T>

这个类不是简单的 XML 序列化,而是顺手把实际项目里容易遇到的问题也考虑进去了。

类头上我写得很直接:

/// 1. XML 序列化/反序列化 /// 2. 同文件互斥锁 /// 3. .tmp 临时文件写入 /// 4. .bak 备份文件 /// 5. 主文件/备份失败时自动重建默认文件 public class XmlConfigStore<T> where T : new()

这一层我觉得很有必要。

因为项目里参数文件这类东西,最怕的不是“不会写”,而是:

  • 写到一半断了
  • 文件坏了
  • 多线程同时碰到了
  • 配置丢了以后现场没法跑

所以我后来会觉得:

参数界面封装,不只是界面问题。
参数读写这件事,也必须一起做稳。


七、最后一层:窗体调用层,只负责“把这些层接起来”

前面这些层拆开以后,窗体层反而会简单很多。

像我现在窗体加载时,流程已经比较清楚:

private void frmparamtest_Load(object sender, EventArgs e) { string path = Path.Combine(Application.StartupPath, "Products", "cla_Para.xml"); _store = new XmlConfigStore<cla_Para>(path, () => new cla_Para()); BuildEditor(); LoadPara(); }

构建编辑器也很直接:

private void BuildEditor() { _editor = new DynamicParaEditor<cla_Para>( ClaParaSchema.Create(), ClaParaSchema.CreateLayout(), GetCurrentPermission()); _editor.Build(tabControl1); }

保存时也很清楚:

_editor.SaveToObject(_para); bool ok = _store.Write(_para, out err);

这一层我现在尽量让它保持轻。

也就是说,窗体不要再去负责:

  • 具体布局
  • 控件创建
  • 字段映射
  • 配置读写细节

窗体只负责把前面几层接起来。

这样后面新做一个参数页时,窗体代码就不会越来越重。


八、我现在最满意的一点:这套结构已经不只服务一个页面了

如果一套封装只在一个地方用过一次,那它更像是“重写了一版代码”。

但我现在这套不是。

从前面也能看到,我现在已经把同样的思路用在了:

  • 基础参数
  • 修正参数
  • 配方参数

也就是说,这套拆法已经不是只服务一个窗体了。

这也是我觉得它值得继续写下去的原因。

因为这说明它不是概念,而是真正开始复用了。


九、这篇先总结一下

这一篇我主要想讲清楚一件事:

我的参数框架不是一个类,而是分层拆出来的一套结构。

现在这套参数界面封装,我是这样拆的:

参数数据层

负责参数有哪些。

参数描述层

负责参数怎么显示。

界面生成层

负责把描述变成真正的参数页。

配置读写层

负责把参数保存和恢复做稳。

窗体调用层

负责把前面这些层接起来。

我现在越来越觉得,这种拆法的价值,不只是代码看起来更清楚。

而是后面会带来很直接的收益:

  • 新建参数页更快
  • 新增参数更快
  • 改分组和显示名更方便
  • 保存读取更统一
  • 权限控制更集中
  • 后期维护更轻

下一篇我准备继续往下写:

新增一个参数页面,我现在是怎么接进去的。

也就是从:

  • 新建参数类
  • 配 schema
  • 配布局
  • 生成界面
  • 接保存和读取

把整个流程再走一遍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询