【C#桌面客户端系列学习-17】选项卡+动态创建表单+自适应

发布时间:2026/10/24 6:38:40

【C#桌面客户端系列学习-17】选项卡+动态创建表单+自适应 目录一、实现效果二、效果图三、实现步骤1、创建窗体并设置窗体属性2、添加选项卡控件3、动态创建表单4、逻辑说明一、实现效果在选项卡里动态创建多个表单保存时获取所有表单的数据。1、打开一个新的窗体新窗体尺寸大小是当前电脑屏幕的80%不显示最大化/最小化窗口大小不能拖拉。2、选项卡点击保存按钮获取所有选项卡表单的内容。3、每个选项卡下都动态生成表单并且每个表单大小会跟随窗口大小自适应。表单创建及布局均使用代码实现不使用设计器的繁琐操作下面的代码可以直接复用方法都是封装好的可以添加多个表单。二、效果图三、实现步骤1、创建窗体并设置窗体属性创建窗体并命名为windowFixed。选中窗体设置属性AutoScaleModeFontFormBorderStyleFixedSingleMaximizeBoxFalseMinimizeBoxFalseStartPositionCenterScreen2、添加选项卡控件① 拖拽panel到窗体命名panelBottom设置Dock属性为Bottom并添加保存按钮命名为btnSave② 拖拽TabControl到窗体并命名为tabMain选中选项卡设置属性AppearanceFlatButtonsDockFillTabPages添加选项卡page注意顺序一定要先添加panelBottom再添加选项卡panelBottom不要放在选项卡里否则的话点击其他选项卡底部按钮就不会显示。3、动态创建表单using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Controls.Primitives; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; namespace test { public partial class windowFixed : Form { public windowFixed() { InitializeComponent(); // 调用表单初始化 InitForm(); } // 设置窗口初始状态 private void windowFixed_Load(object sender, EventArgs e) { // 1. 获取当前屏幕的工作区不包含底部任务栏 Rectangle screenArea Screen.PrimaryScreen.WorkingArea; // 2. 计算屏幕的 80% int targetWidth (int)(screenArea.Width * 0.8); int targetHeight (int)(screenArea.Height * 0.8); // 3. 赋值给当前窗体 this.Width targetWidth; this.Height targetHeight; // 4. 手动计算并在屏幕正中间显示 this.StartPosition FormStartPosition.Manual; this.Location new Point( screenArea.Left (screenArea.Width - this.Width) / 2, screenArea.Top (screenArea.Height - this.Height) / 2 ); } // 创建表单 // 初始化表单 private void InitForm() { // 遍历每一个选项卡给它们创建表单 foreach (TabPage page in tabMain.TabPages) { CreateForm(page); } } /// summary /// 动态创建表单 /// /summary /// param namepage/param private void CreateForm(TabPage page) { // 1. 创建 SplitContainer 分左右两列 SplitContainer split new SplitContainer(); split.Dock DockStyle.Fill; split.Orientation Orientation.Vertical; page.Controls.Add(split); split.SplitterDistance 400; // 2. 左侧流式布局 FlowLayoutPanel leftFlow new FlowLayoutPanel(); leftFlow.Dock DockStyle.Fill; leftFlow.FlowDirection FlowDirection.TopDown; leftFlow.WrapContents false; leftFlow.Padding new Padding(10); split.Panel1.Controls.Add(leftFlow); // 3. 右侧流式布局 FlowLayoutPanel rightFlow new FlowLayoutPanel(); rightFlow.Dock DockStyle.Fill; rightFlow.FlowDirection FlowDirection.TopDown; rightFlow.WrapContents false; rightFlow.Padding new Padding(10); split.Panel2.Controls.Add(rightFlow); // 4. 自适应事件 split.SizeChanged (s, e) { if (split.Width 100) { // 左右两侧比例为46 int targetDistance (int)(split.Width * 0.4); if (targetDistance split.Panel1MinSize targetDistance split.Width - split.Panel2MinSize) { split.SplitterDistance targetDistance; } } }; leftFlow.SizeChanged (s, e) ResizeRowsWidth(leftFlow); rightFlow.SizeChanged (s, e) ResizeRowsWidth(rightFlow); // 5. 填数据根据选项卡的名字Name填入不同的表单字段 switch (page.Name) { case baseInfo: AddInputRow(leftFlow, * HIS中药名称, HisName); AddInputWithButtonRow(leftFlow, * 简 拼, jp, 自动生成, BtnAutoGenerate_Click); AddComboRow(leftFlow, * 剂 型, cboDosage, new string[] { 中药配方颗粒, 中成药 }); AddRadioRow(leftFlow, * 性 别, sex, new string[] { 男, 女 }, 男); AddCheckBoxRow(leftFlow, 伴随症状, zhengzhuang, new string[] { 恶心, 呕吐, 头晕, 皮疹 }); AddMultilineRow(rightFlow, * 功 能, gongneng, 100); AddMultilineRow(rightFlow, * 主 治, zhuzhi, 120); break; case syz: AddMultilineRow(leftFlow, * 适应症, syz, 150); AddMultilineRow(rightFlow, * 禁忌说明, shuoming, 200); break; case jinji: AddMultilineRow(leftFlow, * 禁忌症, jinji, 250); break; } } // 辅助方法 // 辅助生成单行输入框 private void AddInputRow(FlowLayoutPanel parent, string labelText, string fieldName) { Panel row new Panel { Width parent.Width - 25, Height 35, Margin new Padding(0, 10, 0, 0) }; Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.MiddleRight }; TextBox txt new TextBox { Dock DockStyle.Fill, Margin new Padding(0, 8, 0, 0), Tag fieldName }; row.Controls.Add(txt); row.Controls.Add(lbl); parent.Controls.Add(row); } // 辅助生成带右侧按钮的单行输入框 private void AddInputWithButtonRow(FlowLayoutPanel parent, string labelText, string fieldName, string btnText, EventHandler btnClick) { // 1. 外层行容器 Panel row new Panel { Width parent.Width - 25, Height 35, Margin new Padding(0, 10, 0, 0) }; // 2. 标签 (靠左) Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.MiddleRight }; // 3. 按钮 (靠右) - 直接作为行容器的子控件 Button btn new Button(); btn.Text btnText; btn.Width 80; btn.Dock DockStyle.Right; // 直接 Dock 靠右不用套容器 btn.Margin new Padding(5, 5, 0, 5); // 设置 Margin 实现垂直居中上下缩进 5 像素 btn.BackColor Color.Chocolate; btn.ForeColor Color.White; btn.FlatStyle FlatStyle.Flat; btn.FlatAppearance.BorderSize 0; btn.Cursor Cursors.Hand; if (btnClick ! null) btn.Click btnClick; // 4. 输入框 (填满中间) TextBox txt new TextBox { Dock DockStyle.Fill, Margin new Padding(0, 8, 0, 0) , Tag fieldName }; // 5. 组装⚠️必须严格按照以下顺序Fill 最先加Left/Right 后加 row.Controls.Add(txt); // 首先让 TextBox 填满 row.Controls.Add(btn); // 然后把右侧空间切走给 Button row.Controls.Add(lbl); // 最后把左侧空间切走给 Label parent.Controls.Add(row); // 6. 自适应宽度 parent.SizeChanged (s, e) { row.Width parent.ClientSize.Width - 25; }; } // 辅助生成下拉框 private void AddComboRow(FlowLayoutPanel parent, string labelText, string fieldName, string[] items) { Panel row new Panel { Width parent.Width - 25, Height 35, Margin new Padding(0, 10, 0, 0) }; Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.MiddleRight }; ComboBox cbo new ComboBox(); cbo.Tag fieldName; cbo.Dock DockStyle.Fill; cbo.DropDownStyle ComboBoxStyle.DropDownList; cbo.Margin new Padding(0, 8, 0, 0); // 和 TextBox 保持一致的垂直间距 if (items ! null items.Length 0) { cbo.Items.AddRange(items); cbo.SelectedIndex 0; } row.Controls.Add(cbo); row.Controls.Add(lbl); parent.Controls.Add(row); } // 辅助生成多行输入框 private void AddMultilineRow(FlowLayoutPanel parent, string labelText, string fieldName, int height) { Panel row new Panel { Width parent.Width - 25, Height height 10, Margin new Padding(0, 10, 0, 0) }; Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.TopRight }; TextBox txt new TextBox { Multiline true, ScrollBars ScrollBars.Vertical, Dock DockStyle.Fill, Margin new Padding(0, 3, 0, 0) , Tag fieldName }; row.Controls.Add(txt); row.Controls.Add(lbl); parent.Controls.Add(row); } // 辅助生成单选框支持多个选项排在一行 private void AddRadioRow(FlowLayoutPanel parent, string labelText, string fieldName, string[] options, string defaultValue ) { // 1. 外层行容器 Panel row new Panel { Width parent.Width - 25, Height 35, Margin new Padding(0, 10, 0, 0) }; // 2. 标签 (靠左) Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.MiddleRight }; // 3. 选项容器 FlowLayoutPanel optionPanel new FlowLayoutPanel { Dock DockStyle.Fill, FlowDirection FlowDirection.LeftToRight, WrapContents false, Margin new Padding(0, 8, 0, 0) }; // 4. 循环创建单选框 if (options ! null) { foreach (var option in options) { RadioButton rb new RadioButton(); rb.Text option; rb.AutoSize true; rb.Margin new Padding(0, 0, 15, 0); // 核心给每个单选框打上相同的 Tag rb.Tag fieldName; // 如果传入了默认选中的值则勾选 if (option defaultValue) { rb.Checked true; } optionPanel.Controls.Add(rb); } } // 5. 组装⚠️注意添加顺序Fill 的最先加Left 的后加 row.Controls.Add(optionPanel); row.Controls.Add(lbl); parent.Controls.Add(row); } // 辅助生成多选框支持多个选项排在一行 private void AddCheckBoxRow(FlowLayoutPanel parent, string labelText, string fieldName, string[] options) { // 1. 外层行容器 Panel row new Panel { Width parent.Width - 25, Height 35, Margin new Padding(0, 10, 0, 0) }; // 2. 标签 (靠左) Label lbl new Label { Text labelText, Width 100, Dock DockStyle.Left, TextAlign ContentAlignment.MiddleRight }; // 3. 选项容器 (横向排列) FlowLayoutPanel optionPanel new FlowLayoutPanel { Dock DockStyle.Fill, FlowDirection FlowDirection.LeftToRight, WrapContents false, Margin new Padding(0, 8, 0, 0) }; // 4. 循环创建多选框 if (options ! null) { foreach (var option in options) { CheckBox cb new CheckBox(); cb.Text option; cb.AutoSize true; cb.Margin new Padding(0, 0, 15, 0); // 给每个多选框打上相同的 Tag cb.Tag fieldName; optionPanel.Controls.Add(cb); } } // 5. 组装 row.Controls.Add(optionPanel); row.Controls.Add(lbl); parent.Controls.Add(row); } // 统一调整流式面板里每一行的宽度 private void ResizeRowsWidth(FlowLayoutPanel parent) { foreach (Control ctrl in parent.Controls) { ctrl.Width parent.ClientSize.Width - 25; // 25 是留给滚动条的余量 } } private void BtnAutoGenerate_Click(object sender, EventArgs e) { MessageBox.Show(执行自动生成逻辑); } // 递归查找指定Tag的控件并返回它的值 --- 适用于单行输入框、多行输入框、下拉框、单选按钮 private string GetValueByTag(Control parent, string targetTag) { foreach (Control ctrl in parent.Controls) { if (ctrl.Tag ! null ctrl.Tag.ToString() targetTag) { if (ctrl is TextBox txt) return txt.Text; if (ctrl is ComboBox cbo) return cbo.Text; if (ctrl is RadioButton rb rb.Checked) return rb.Text; if (ctrl is CheckBox cb cb.Checked) return cb.Text; } // 如果没找到且控件有子控件继续深入查找 if (ctrl.HasChildren) { string result GetValueByTag(ctrl, targetTag); if (!string.IsNullOrEmpty(result)) return result; } } return ; // 没找到返回空字符串 } // 递归查找指定 Tag 的所有多选框并返回选中项的拼接字符串如 恶心,头晕--- 适用于多选框 private string GetCheckedBoxesValueByTag(Control parent, string targetTag) { Liststring checkedValues new Liststring(); // 内部递归函数 void FindCheckBoxes(Control ctrl) { foreach (Control child in ctrl.Controls) { // 如果找到了带有指定 Tag 的多选框且被选中了 if (child is CheckBox cb cb.Tag ! null cb.Tag.ToString() targetTag cb.Checked) { checkedValues.Add(cb.Text); } if (child.HasChildren) { FindCheckBoxes(child); } } } FindCheckBoxes(parent); // 将选中的值用逗号拼接如果没选就返回空字符串 return string.Join(,, checkedValues); } // 保存数据 /// summary /// 保存数据 /// /summary /// param namesender/param /// param namee/param private void btnSave_Click(object sender, EventArgs e) { // 用于存放所有提取出来的数据 Dictionarystring, string allData new Dictionarystring, string(); // 1.遍历所有选项卡 foreach (TabPage page in tabMain.TabPages) { // 2.根据选项卡的Name提取对应的值 switch (page.Name) { case baseInfo: allData[HisName] GetValueByTag(page, HisName); allData[jp] GetValueByTag(page, jp); allData[cboDosage] GetValueByTag(page, cboDosage); allData[sex] GetValueByTag(page, sex); allData[gongneng] GetValueByTag(page, gongneng); allData[zhuzhi] GetValueByTag(page, zhuzhi); // 复选框判断是否为空 allData[zhengzhuang] GetCheckedBoxesValueByTag(page, zhengzhuang); break; case syz: allData[syz] GetValueByTag(page, syz); allData[shuoming] GetValueByTag(page, shuoming); break; case jinji: allData[jinji] GetValueByTag(page, jinji); break; } } // 3.测试输出 string result 收集到的全部数据 \n; foreach (var kvp in allData) { result ${kvp.Key}:{kvp.Value}\n; } MessageBox.Show(result, 保存结果); } } }4、逻辑说明① 在windowFixed_Load事件中先设置窗口初始状态即窗口大小是当前电脑屏幕的80%。② 在构造函数中调用表单初始化。③ 由于可能会有多个选项卡所有这里使用循环遍历每一个选项卡并给他们创建表单。InitForm()方法里的tabMain是选项卡的名称一定要一致。④ 表单布局使用SplitContainerFlowLayoutPanel的方式控制表单布局使用SplitContainer将面板一分为二占用空间比例为4:6使用FlowLayoutPanel进行流式布局。⑤ 表单填充根据不同的选项卡名称设置对应选项卡下的表单并利用相关辅助方法设置表单的属性表单类型包括单行输入框、多行输入框、单选按钮、下拉框、复选框。⑥ 数据获取点击“保存”按钮时获取所有选项卡的值。如果添加其他表单直接在CreateForm()放在中的对应的选项卡下直接用代码添加即可。
延伸阅读

更多相关文章

2026/10/24 6:33:39

HDLHorse重磅发布|大幅提升设计仿真的效率

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/24 6:33:39

全网首发拆解JEV决策大模型为什么爆火???

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/24 7:33:42

科莱特 SAP FICO 学员真实经历:一个普通财务人的成长复盘

前面四篇,我写了为什么转 SAP、怎么学、怎么面、怎么进项目活下来。这一篇,是这个系列的收尾。不聊操作、不聊面试技巧。我想聊聊这整件事给我的认知冲击,走到今天,脑子里真正变了什么。如果你也在犹豫要不要转、转了之后值不值、…

2026/10/24 7:33:42

Swift 常量详解:let 与 const 的正确使用方式

1. 引言在 Swift 编程语言中,常量是构建安全、可预测代码的基础。与变量不同,常量一旦赋值便不可更改,这不仅能防止意外修改,还能让代码的意图更加清晰。本文将深入讲解 Swift 常量的定义、使用场景以及最佳实践,并通过…

2026/10/24 7:33:42

坚果云收件箱的10个神用法:从收集周报到客户合同文件自动归档

文章目录一、文件收集的「三大灾难」二、收件箱的核心机制2.1 收件箱 vs 传统收集方式2.2 收件箱的创建步骤三、10个神用法逐一拆解用法一:团队周报收集用法二:学生作业提交用法三:客户合同签约用法四:供应商报价收集用法五&#…

2026/10/24 7:33:42

SSD1322 OLED驱动芯片底层机制拆解:从接口时序到灰度表

简介:SSD1322数据手册是Solomon Systech公司发布的高性能有机发光二极管驱动控制器官方技术文档,面向显示驱动开发工程师、嵌入式硬件设计人员和屏体调试工程师,用于解决芯片选型、引脚连接、寄存器配置以及显示时序匹配等问题。资源以单个PD…

2026/10/24 7:33:42

响应式数据流实战:从零构建实时数据处理内核

1. 从“rea”这个标题说起:一个被低估的缩写背后藏着什么第一次看到“rea”这个标题,我承认自己愣了几秒。它太短了,短到像是一个被截断的单词,又像是一个随手敲下的代号。但恰恰是这种极简的标题,往往藏着最值得深挖的…

2026/10/24 7:28:42

多智能体协作框架agency-agents:架构设计与工程实践

1. 从"agency-agents"这个标题说起:一个多智能体协作框架的完整拆解第一次看到"agency-agents"这个命名的时候,我的直觉是:这大概率是一个围绕"代理"和"代理机构"概念构建的多智能体协作系统。拆开来…

2026/10/23 22:56:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/24 0:02:40

MATLAB缩进快捷键与批量格式化实战指南

1. 被自动缩进折磨的那些年如果你写过MATLAB代码,大概率经历过这种场景:从某个文档、网页或者聊天记录里复制了一段代码,粘贴进编辑器之后,每一行前面都带着莫名其妙的空格,有的行首是四个空格,有的是一个T…

2026/10/24 0:02:40

用Poco实现Qt风格信号与槽的跨平台C++方案

简介:本资源是一份面向C中级开发者的技术实践工程,聚焦于在非Qt框架环境下模拟实现Qt核心通信机制——信号与槽,借助Poco网络库的事件系统完成解耦式对象通信。适用于需在跨平台C项目中引入松耦合回调机制、但受限于无法集成Qt依赖的开发场景…

2026/10/24 0:02:40

ESXi裸机虚拟化安装指南:从硬件兼容到虚拟机部署

1. 为什么还要折腾ESXi:从一台闲置机器说起手里攒了几台淘汰下来的迷你主机,配置不算差,i5加16G内存,扔了可惜,装个桌面系统又觉得浪费。这种场景下,ESXi就成了一个绕不开的选择。它是一套裸机虚拟化平台&a…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑