前端winform开发
新增页面
在你的前端项目,比如Hello.Wnform里面新建一个Forms文件夹,我们一般喜欢把页面放到这个文件夹里面。
按照开发规范,我们建议每个页面的名称如: FrmPA1000
参考下面的项目文件结构:

画面你可以安装dx控件的使用说明自己设计:

页面的主要逻辑的代码也很简单:(你的页面必须继承 HmxBaseForm )
namespace DDH.TQMES.Winform.SIM.Forms
{
public partial class FrmMQ1010 : HmxBaseForm
{
public FrmMQ1010()
{
InitializeComponent();
this.ViewControl.Config(this.gridControl1)
.AllowSyncRowStateToCheckboxSelection = true;
}
private async void btnQuery_Click(object sender, EventArgs e)
{
await btnQuery.ExecuteAsync(LoadData);
}
async Task LoadData()
{
using var wait = gridControl1.ShowLoadingPanel();
var list = await dpc.Proxy<IOneCardTransferAppService>().QueryData(new OneCardRecordInputDto
{
TimeType = "3",
TimeRange = ucTimeRange1.Value,
MtrlNo = textEdit2.EditValue as string,
OperateType = textEdit1.EditValue as string,
MachId = txtMachId.Text.Trim()
});
tmwMeasureBindingSource.DataSource = list;
gridView1.RefreshData();
gridView1.BestFitColumns();
}
private async void FrmMQ1010_Load(object sender, EventArgs e)
{
ucTimeRange1.Value = new TimeRange(
DateTime.Today,
DateTime.Today.AddDays(1).AddSeconds(-1)
);
await LoadMaterialsByProduct();
}
private async Task LoadMaterialsByProduct()
{
textEdit2.Enabled = false;
var queryDto = new QueryMaterialDto
{
CValidstate = "1",
};
var materials = await SF.Proxy<ITpa1011AppService>().QueryMaterialInfo(queryDto);
var displayData = materials.Select(m => new
{
DisplayText = $"{m.CMaterialCode}-{m.CMaterialDesc}",
Value = m.CMaterialCode,
MaterialCode = m.CMaterialCode,
MaterialDesc = m.CMaterialDesc
}).ToList();
textEdit2.Properties.DataSource = displayData;
textEdit2.Properties.DisplayMember = "DisplayText";
textEdit2.Properties.ValueMember = "Value";
textEdit2.Properties.TextEditStyle = DevExpress.XtraEditors.Controls.TextEditStyles.Standard;
textEdit2.Properties.View.OptionsFind.AlwaysVisible = true;
textEdit2.Properties.View.OptionsFind.FindDelay = 100;
textEdit2.Properties.View.OptionsFind.HighlightFindResults = true;
textEdit2.Properties.View.Columns.Clear();
var colCode = new DevExpress.XtraGrid.Columns.GridColumn();
colCode.FieldName = "MaterialCode";
colCode.Caption = "物料代码";
colCode.Visible = true;
colCode.Width = 120;
var colDesc = new DevExpress.XtraGrid.Columns.GridColumn();
colDesc.FieldName = "MaterialDesc";
colDesc.Caption = "物料描述";
colDesc.Visible = true;
colDesc.Width = 200;
textEdit2.Properties.View.Columns.AddRange(new[] { colCode, colDesc });
textEdit2.Properties.PopupFormSize = new System.Drawing.Size(400, 300);
textEdit2.Properties.ImmediatePopup = true;
textEdit2.Properties.AutoComplete = false;
textEdit2.Enabled = true;
textEdit2.EditValue = null;
}
private async void btnInstorage_Click(object sender, EventArgs e)
{
var selectedRecord = (tmwMeasureBindingSource.DataSource as List<OneCardRecordItemDto> ?? [])
.Where(l => l.Selected)
.Where(l => l.DUnloadFlag != YN.Y)
.ToList();
if (selectedRecord.IsNullOrEmpty())
{
MsgBox.Show("请至少选择一条未卸货的记录进行入库操作");
return;
}
if (MsgBox.ShowYesNo($"确认操作?数量{selectedRecord.Count}") != DialogResult.Yes)
{
return;
}
using var dialog = this.gridControl1.ShowLoadingPanel();
var errorMsg = new StringBuilder();
var i = 0;
foreach (var item in selectedRecord)
{
try
{
await SF<IStorageAppSerevice>.Proxy.OnCardInStorage(item);
i++;
}
catch (Exception ex)
{
errorMsg.AppendLine($"{item.LogisticsNo} {item.CMtrlName}入库失败:{ex.GetBaseException()?.Message ?? ex.Message}");
}
}
MsgBox.Show($"入库成功{i}/{selectedRecord.Count}\r\n{errorMsg}");
await LoadData();
}
private async void btnH2OUpdate_Click(object sender, EventArgs e)
{
var selectedRecord = (tmwMeasureBindingSource.DataSource as List<OneCardRecordItemDto> ?? [])
.Where(l => l.Selected)
.Where(l => l.DUnloadFlag == YN.Y)
.ToList();
if (selectedRecord.IsNullOrEmpty())
{
MsgBox.Show("请至少选择一条已卸货的记录进行水分调整操作");
return;
}
if (MsgBox.ShowYesNo($"确认操作?数量{selectedRecord.Count}") != DialogResult.Yes)
{
return;
}
using var dialog = this.gridControl1.ShowLoadingPanel();
var errorMsg = new StringBuilder();
var i = 0;
foreach (var item in selectedRecord)
{
try
{
await SF<IStorageAppSerevice>.Proxy.UpdateStorageByH2o(new List<StorageH2oDiffDto>
{
new StorageH2oDiffDto
{
Mtrl = item.CMtrlNo,
BusNo = item.LogisticsNo,
DDate = DateTime.Today,
BatchNo = "QC260530074",
H2o = 5M / 100
}
});
i++;
}
catch (Exception ex)
{
errorMsg.AppendLine($"{item.LogisticsNo} {item.CMtrlName}入库失败:{ex.GetBaseException()?.Message ?? ex.Message}");
}
}
MsgBox.Show($"水分更新成功{i}/{selectedRecord.Count}\r\n{errorMsg}");
}
private async void btnCancel_Click(object sender, EventArgs e)
{
var selectedRecord = (tmwMeasureBindingSource.DataSource as List<OneCardRecordItemDto> ?? [])
.Where(l => l.Selected)
.Where(l => l.DUnloadFlag == YN.Y)
.ToList();
if (selectedRecord.IsNullOrEmpty())
{
MsgBox.Show("请至少选择一条已卸货的记录进行水分调整操作");
return;
}
if (MsgBox.ShowYesNo($"确认操作?数量{selectedRecord.Count}") != DialogResult.Yes)
{
return;
}
using var dialog = this.gridControl1.ShowLoadingPanel();
var errorMsg = new StringBuilder();
var i = 0;
foreach (var item in selectedRecord)
{
try
{
await SF<IStorageAppSerevice>.Proxy.Cancel([item.LogisticsNo]);
i++;
}
catch (Exception ex)
{
errorMsg.AppendLine($"{item.LogisticsNo} {item.CMtrlName}撤销入库失败:{ex.GetBaseException()?.Message ?? ex.Message}");
}
}
MsgBox.Show($"撤销入库成功{i}/{selectedRecord.Count}\r\n{errorMsg}");
}
}
}实际上这里最重要的是如何和后端服务交互:
var list = await dpc.Proxy<IOneCardTransferAppService>().QueryData(……)
这里直接使用hmx框架封装的dpc客户端即可。
配置菜单与权限
如果你的页面继承了HmxBaseForm,没有编译错误,你启动hmx框架,在系统管理—资源管理里面就可以配置菜单了:


测试与运行
因为我们最新hmx框架是完全前后端分离的,所以你在测试的时候要确保后端服务已经启动,或者同时启动。
我们一般这么设置:

这样你就可以很好的同时运行和调试前端和后端了。
常见组件使用
消息提示框
// 基本提示
MsgBox.Show("操作成功");
MsgBox.ShowWarning("请注意");
MsgBox.ShowError("出错了");
MsgBox.ShowYesNo("确认删除?"); // 返回 DialogResult
// Toast 风格提示(自动消失)
MsgBox.ShowAlertSuccess("保存成功");
MsgBox.ShowAlertFailure("保存失败");
MsgBox.ShowAlert("提示信息");
// 显示异常(自动截图 + 写日志)
MsgBox.ShowError(ex);
// 状态栏消息
MsgBox.ShowStatus("已选择 3 行");消息等待框
// using 语法,自动关闭
using var wait = new WaitDialog("正在加载...");
await SomeAsyncOperation();
// wait dispose 时自动关闭对话框自动控件管线
ViewControl 是框架的自动控件初始化管线——窗体上放的 DevExpress 控件无需手动配置,框架在 HandleCreated 时自动发现并执行对应的 Executor。
工作原理:
窗体构造
└─ new ViewControl(this) // 持有当前 Form 引用
HandleCreated 触发
└─ LoadComponents() // 反射收集所有 Component 字段
└─ Start()
├─ 同步 Executor // AsyncRun = false 的先执行
└─ 异步 Executor // AsyncRun = true 的后执行
└─ 逐个 Executor.Excute(component, options)执行流程:
- 框架用反射获取窗体上所有 Component 类型的字段(继承链上直到 HmxBaseForm 为止)
- 对每个 Component,查找已注册的 IViewControlExecutor,匹配规则是 component.GetType().HasParentType(executor.CompnentType)
- 调用 executor.Excute(component, options) 执行自动配置
- NoCompnentViewControlExecutor 的子类不绑定特定控件,对整个 Form 执行(如多语言、GlueForm 生命周期管理)
| Executor | 绑定类型 | 同步/异步 | 自动完成的事 |
|---|---|---|---|
| ViewControlExecutor_GridControl | GridControl | 异步 | 行号绘制、右键菜单、checkbox 全选、选中行高亮、布局保存、鼠标滚轮关闭编辑器、回车跳下一列、变更状态颜色 |
| ViewControlExecutor_GridControl_PopupMenu | GridControl | 同步 | 右键弹出菜单(复制/导出/打印/统计等) |
| ViewControlExecutor_TreeList | TreeList | 异步 | 多选、右键菜单、checkbox 修复、布局保存 |
| ViewControlExecutor_BaseEdit | BaseEdit 及子类 | 异步 | ComboBox 下拉行数、AllowNullInput 清空按钮、DateEdit TodayClick、SearchLookUpEdit 自动列宽、F3 配置数据字典、多语言翻译下拉项、验证规则 |
| ViewControlExecutor_SimpleButton | SimpleButton | 同步 | 事件拦截(防止重复点击)、按钮权限校验(无权限自动隐藏) |
| ViewControlExecutor_LayoutControl | LayoutControl | 异步 | 自动计算高度、布局保存、右键菜单 |
| ViewControlExecutor_Localization | 无(NoComponent) | — | 自动多语言翻译(按钮/列头/标签/布局项等) |
| ViewControlExecutor_GlueForm | 无(NoComponent) | — | Form 关闭时自动停 Timer、Dispose 时清空所有字段和事件(防内存泄漏) |
在窗体构造函数或 Load 中,通过 ViewControl.For(component) 获取并修改 Options:
public FrmMyPage()
{
InitializeComponent();
// 关闭某个 GridControl 的自动配置
ViewControl.For(gridControl1).Enable = false;
// GridControl 专属 Options
var gridOps = ViewControl.For(gridControl1) as ViewControlExecutorOptions_GridControl;
gridOps.AllowColumnAutoWidth = true;
gridOps.AllowMultipleCheckbox = false;
gridOps.AllowGridviewDrawRowIndicator = false;
gridOps.AllowSummaryInfoInStatusBar = false;
// 某个控件不参与多语言翻译
ViewControl.For(simpleButton1).SupportForMultipleLanguages = false;
}| 属性 | 默认值 | 说明 |
|---|---|---|
| AllowColumnAutoWidth | false | 列自动宽度 |
| AllowMultipleCheckbox | true | 列头 checkbox 全选 |
| AllowGridviewDrawRowIndicator | true | 行号绘制 |
| AllowGridviewDrawRowCell | true | 选中行高亮 |
| AllowSummaryInfoInStatusBar | true | 状态栏显示选中统计 |
| AllowSaveLayoutToXml | false | 布局保存(右键菜单可切换) |
| AllowSyncRowStateToCheckboxSelection | false | 行选择状态同步到 checkbox |
| UseCellMergeWithPreColumn | false | 按前列合并单元格 |
数据门户请求器
dpc 在 HmxBaseForm 中已初始化,直接用:
// 调用远程服务
var result = await dpc.Proxy<IMyService>().GetDataAsync();
// 带参数
var result = await dpc.Proxy<IMyService>().GetByIdAsync(id);多语言服务
// 在代码中
var text = LM.L("保存成功");
// 在字符串中插值
var msg = LM.L("用户 [{0}] 登录成功").Args(userName);多语言翻译由 ViewControlExecutor_Localization 自动完成 ——窗体上的BarButtonItem、GridColumn、LabelControl、LayoutControlItem、SimpleButton、GroupControl、TabNavigationPage、TreeListColumn 的文本会自动翻译。
不需要手动调用 LM.L() 来翻译控件文本,只需要在设计时用中文命名,运行时框架自动翻译。
按钮权限管控
ViewControlExecutor_SimpleButton 在同步阶段自动校验:
- 获取当前窗体的 Id(菜单 ID)
- 查询按钮是否受控(QueryButtonsRescAsync)
- 受控且无权限 → btn.Visible = false
不需要写任何权限代码。窗体上的 SimpleButton 只要 Name 与系统配置的按钮编码一致,框架自动处理。
控件扩展方法
// 线程安全更新 UI
control.SafeInvoke(() => { control.Text = "updated"; });
// 获取/设置扩展 Tag(基于 Handle 的并发安全存储)
gridControl.SetExTag("myKey", 42);
var val = gridControl.GetExTag<int>("myKey");
// 获取根窗体
var rootForm = someControl.GetRootForm();
// 控件水印
textBox.BindWaterMark("请输入搜索关键字");
// 按钮异步执行(自动禁用/恢复按钮)
await btn.ExecuteAsync(async () =>
{
await SomeLongOperation();
});使用SVG 图标
// 加载单个 SVG 到 ImageOptions
ViewAction.LoadSvgImage(this.IconOptions, "sys");
// 加载菜单图标集合
ViewAction.InitMenuIcons(svgImageCollection1);
// 从本地 ./images/menu-svg-icons/ 目录加载所有 SVG布局持久化
ViewControlExecutor_GridControl、ViewControlExecutor_TreeList、ViewControlExecutor_LayoutControl 都自动注册了 DxLayoutConfigActor:
- 窗体打开时从 config/layout/{userId}/ 读取 XML 恢复布局
- 窗体关闭时自动保存
- 右键菜单可切换「自动保存布局」/「重置并取消自动保存布局」
数据字典绑定
ComboBox 的 AccessibleName 设为字典编码,框架自动从服务器拉取数据:
// 设计器中:设置 ComboBoxEdit(下拉控件一般都有) 的 AccessibleName = "ORDER_STATUS"
// 运行时自动绑定数据字典
// 管理员按 F3 可打开字典维护窗口数据变更追踪与保存
// 初始化可追踪列表
bindingSource1.InitializeObservableList<OrderDto>();
// 或从服务器获取后包装
bindingSource1.DataSource = data.AsTrackable();
// 保存(自动识别增/删/改)
await bindingSource1.SaveChanges<OrderDto>();
// 获取原始数据(无追踪)
var original = bindingSource1.GetNoTrackingList<OrderDto>();设计器扩展属性
HmxControlExtenderProvider 提供一组设计器属性,在 Visual Studio 属性窗口中可用:
| 扩展属性 | 目标控件 | 说明 |
|---|---|---|
| AutoHmxStyle | SimpleButton | 自动设置按钮基础样式 |
| IconifyIcon | SimpleButton | 格式 icon-[ph--acorn],参考 yesicon.app |
| AutoGenerateButtons | StackPanel | 自动生成按钮布局 |
| AutoGenerateColumnCaption | GridControl | 根据实体 DisplayName 自动设置列头 |
| AutoGenerateItemText | LayoutControlItem | 根据实体 DisplayName 自动设置布局项文本 |
| EnableLocalization | 所有 Component | 是否允许自动多语言翻译(默认 true) |
| TemplateData | SpreadsheetControl | 保存/恢复电子表格模板 |
配置文件
前端WinForm使用 appsettings.json 进行配置,主要配置与后端服务的连接信息。
配置文件位置
| 项目 | 配置文件路径 |
|---|---|
| 前端WinForm | admin/Hmx.WinForms.Startup/appsettings.json |
完整配置结构
{
"AppSettings": {
"loglevel": "debug"
},
"ConnectionStrings": {
"SignalR": "http://192.168.132.68:5225",
"dataPortalUrl": "http://127.0.0.1:5225/api"
},
"AllowedHosts": "*"
}配置项详解
1. AppSettings - 应用设置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
loglevel | string | debug | 日志级别:Trace, Debug, Info, Warn, Error, Fatal |
2. ConnectionStrings - 连接字符串
| 配置项 | 必填 | 说明 |
|---|---|---|
SignalR | ✅ | 后端服务的SignalR地址,用于实时通信 |
dataPortalUrl | ✅ | 后端服务的数据访问地址,支持HTTP和TCP两种模式 |
dataPortalUrl 连接格式:
| 模式 | 协议 | 格式 | 示例 | 说明 |
|---|---|---|---|---|
| HTTP | http | http://{host}:{port}/api | http://192.168.1.100:5225/api | 标准HTTP访问,适合大多数场景 |
| TCP | tcp | tcp://{host}:{port}/ | tcp://192.168.1.100:9026/ | 高性能TCP访问,适合大数据量场景 |
带超时参数的连接格式:
http://{host}:{port}/api?timeout=120
tcp://{host}:{port}/?timeout=120| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout | int | 600 | 请求超时时间(秒),默认10分钟 |
注意:框架会根据URL前缀自动识别连接模式(Auto模式)。
代码中读取配置:
// 读取SignalR地址
var signalRUrl = EngineContext.Current.Resolve<IConfiguration>()
.GetSection("ConnectionStrings:SignalR").Get<string>();
// 读取数据访问地址
var dataPortalUrl = DataPortalClientOptionsForServiceClient.DataPortalUrl;配置示例:
{
"ConnectionStrings": {
"SignalR": "http://192.168.132.68:5225",
"dataPortalUrl": "http://192.168.132.68:5225/api"
}
}配置说明
SignalR:用于实时通信功能dataPortalUrl:前端访问后端服务的主要地址,支持HTTP和TCP两种协议
注意:前端WinForm的配置相对简单,主要是与后端服务的连接配置。详细的后端配置请参考后端service开发文档。
前端使用技巧
表格
表格是系统中使用最频繁的控件,用于展示列表数据。
- 基本操作
| 操作 | 方法 |
|---|---|
| 选择行 | 单击行左侧的行号区域 |
| 多选行 | 按住 Ctrl 键逐行点击,或按住 Shift 键选择连续范围 |
| 全选/取消全选 | 点击列头最左侧的 checkbox(如果有) |
| 编辑单元格 | 双击单元格,或选中后按 F2 |
| 复制单元格 | 选中单元格后按 Ctrl+C |
| 粘贴到单元格 | 选中目标单元格后按 Ctrl+V |
| 取消编辑 | 按 Esc 键 |
1.2 排序
单列排序:点击列头,第一次升序,第二次降序,第三次取消排序
多列排序:按住 Shift 键再点击其他列头
取消排序:按住 Ctrl 键点击已排序的列头
- 筛选
每列列头右侧有一个漏斗图标,点击可打开筛选菜单:
文本筛选:输入关键词快速过滤
值列表筛选:勾选/取消勾选具体值
条件筛选:选择"自定义"可设置复杂条件(大于、小于、包含等)
查找面板:按 Ctrl+F 打开顶部查找栏,输入关键词全局搜索。
- 分组
将列头拖拽到表格上方的分组面板即可按该列分组:
拖入多个列可实现多级分组
点击分组行的 + / - 展开/折叠
将列头从分组面板拖回表格即可取消分组
- 列操作
| 操作 | 方法 |
|---|---|
| 调整列宽 | 拖动列头之间的分隔线 |
| 最佳宽度 | 双击列头分隔线,自动适配内容 |
| 移动列 | 拖动列头到目标位置 |
| 隐藏列 | 右键列头 → "列自定义" → 将列拖到自定义面板 |
| 固定列 | 右键列头 → "固定到左侧/右侧",滚动时该列不动 |
滚动与导航
垂直滚动:鼠标滚轮 / 拖动右侧滚动条
水平滚动:底部滚动条 / 按住 Shift + 鼠标滚轮
跳转到首行:Ctrl+Home
跳转到末行:Ctrl+End
跳转到行首/行尾:Home / End
- 行号与统计
行号:表格最左侧自动显示序号(- 标记当前行)
统计面板:表格底部可显示选中行的汇总信息(求和、计数等)
状态栏统计:选中单元格时,底部状态栏会显示统计信息
- 导出与打印
右键表格 → 选择:
- 导出到 Excel:支持 .xlsx 格式
- 导出到 CSV:适合数据分析
- 打印预览:查看打印效果
- 直接打印:发送到打印机
树表格
树结构用于展示层级数据(如组织架构、菜单树)。
- 2.1 基本操作
| 操作 | 方法 |
|---|---|
| 展开节点 | 点击节点左侧的 ▶ 箭头 |
| 折叠节点 | 点击已展开节点的 ▼ 箭头 |
| 全部展开 | 按 Ctrl+Shift+Enter |
| 全部折叠 | 按 Ctrl+Shift+Backspace |
| 选择节点 | 单击节点行 |
| 多选节点 | 按住 Ctrl 键逐个点击 |
| 编辑节点 | 双击单元格或按 F2 |
- 2.2 排序与筛选
与表格操作一致:
点击列头排序
点击列头漏斗图标筛选
支持多列排序
2.3 右键菜单
右键点击节点,可使用:
- 复制:复制选中内容到剪贴板
- 展开所有:展开所有子节点
- 折叠所有:折叠所有子节点
- 自动调整列宽:所有列自适应内容宽度
布局
布局控件用于自动排列表单中的输入框、按钮等控件,确保对齐和自适应。
3.1 特点
控件自动对齐,不会出现错位
窗体缩放时,控件自动调整位置和大小
字体改变时,布局不会错乱
支持分组和标签页
3.2 常见布局元素
| 元素 | 说明 |
|---|---|
| 文本编辑器 | 标准输入框,支持掩码格式化 |
| 下拉选择 | ComboBoxEdit / LookUpEdit |
| 日期选择 | DateEdit 带日历弹窗 |
| 按钮 | SimpleButton |
| 分组框 | GroupControl,将相关控件分组 |
| 分割线 | Separator 项,视觉分隔 |
- 3.3 右键菜单
右键布局区域可使用:
- 重置布局:恢复默认布局
- 自动保存布局:记住用户调整后的布局
标签页
主界面使用 DevExpress DocumentManager 配合 TabbedView 管理所有打开的业务页面,以标签页形式展示。
基本操作:
| 操作 | 方法 |
|---|---|
| 切换页面 | 点击顶部的标签页标题 |
| 关闭单个页面 | 点击标签页标题右侧的 × |
| 关闭所有页面 | 工具栏 → 系统 → "关闭所有页面" |
| 拖拽标签排序 | 拖动标签页标题到目标位置 |
| 拖拽标签到独立窗口 | 将标签页拖出标签栏,自动变为浮动窗口 |
| 双击标签最大化 | 双击标签页标题,该页面独立显示 |
| 全屏/还原 | Ctrl+F11 快捷键切换当前页面全屏 |
标签页右键菜单:
| 菜单项 | 功能 |
|---|---|
| 关闭 | 关闭当前标签页 |
| 关闭其他 | 关闭除当前外的所有标签页 |
| 关闭所有 | 关闭所有标签页 |
| 浮动窗口 | 将当前页面变为独立浮动窗口 |
| 停靠窗口 | 将浮动窗口恢复到标签栏 |
全屏模式:
- 按 Ctrl+F11 进入全屏:隐藏工具栏和导航树,当前页面最大化
- 再按 Ctrl+F11 退出全屏:恢复所有界面元素
编辑器控件
4.1 下拉选择(ComboBoxEdit)
选择值:点击下拉箭头 → 从列表中选择
搜索:输入首字母快速定位(如输入 "完" 定位到"完成")
清空:如果有清空按钮(⊘),点击即可
4.2 查找选择(LookUpEdit / SearchLookUpEdit)
当选项较多时使用查找选择器:
LookUpEdit:单击下拉箭头 → 在弹出列表中选择
SearchLookUpEdit:弹出带搜索栏的表格 → 输入关键词搜索 → 选择行
技巧:SearchLookUpEdit 适合选项超过 20 条的场景,可快速定位。
4.3 日期选择(DateEdit)
点击今天:点击日历中的今天日期,自动选择并关闭
键盘输入:直接输入 2026-07-02 格式
快捷键:Alt+↓ 打开日历弹窗
4.4 多选下拉(CheckedComboBoxEdit)
选择多个值:点击下拉箭头 → 勾选需要的项
全选/取消全选:顶部有全选复选框
取消某项:取消勾选对应的复选框
右键菜单功能
系统为表格和树结构提供了丰富的右键菜单:
- 5.1 表格右键菜单
| 菜单项 | 功能 |
|---|---|
| 查找列 | 快速跳转到指定列(支持模糊搜索) |
| 表单查看 | 以详情卡片形式查看当前行 |
| 显示/隐藏统计面板 | 切换底部汇总区域 |
| 切换表格大/小字体 | 在大字体和小字体间切换 |
| 启用/禁用单元格合并 | 相邻相同值的单元格自动合并 |
| 复制当前数据格 | 复制单元格值到剪贴板 |
| 填充已选单元格 | 将当前单元格值填充到选中区域 |
| 统计已选单元格 | 对选中区域进行求和/平均等统计 |
| 导出到 Excel | 导出当前表格数据 |
| 打印预览 | 预览打印效果 |
| 打印 | 直接打印 |
| 自动保存布局 | 记住列宽、列顺序等调整 |
- 5.2 列头右键菜单
| 菜单项 | 功能 |
|---|---|
| 查找列 | 快速定位列 |
| 列自定义 | 显示/隐藏/移动列 |
| 排序 | 升序/降序/清除排序 |
| 分组 | 按当前列分组 |
| 自动保存布局 | 保存列布局设置 |
键盘快捷键
- 表格通用
| 快捷键 | 功能 |
|---|---|
| ↑ ↓ ← → | 移动焦点 |
| Enter | 跳转到下一列(或确认编辑) |
| Tab | 跳转到下一个可编辑单元格 |
| Shift+Tab | 跳转到上一个可编辑单元格 |
| F2 | 激活单元格编辑 |
| Esc | 取消当前编辑 |
| Home / End | 跳转到行首 / 行尾 |
| Ctrl+Home / Ctrl+End | 跳转到首行首列 / 末行末列 |
| PageUp / PageDown | 向上/向下翻页 |
- 复制粘贴
| 快捷键 | 功能 |
|---|---|
| Ctrl+C | 复制选中单元格 |
| Ctrl+V | 粘贴到选中单元格 |
| Ctrl+X | 剪切选中单元格 |
- 搜索与筛选
| 快捷键 | 功能 |
|---|---|
| Ctrl+F | 打开查找面板 |
| F3 / Shift+F3 | 跳转到下一个/上一个搜索结果 |
| Ctrl+Shift+F | 打开高级查找面板 |
- 选择
| 快捷键 | 功能 |
|---|---|
| Ctrl+Space | 切换当前行/单元格选中状态 |
| Shift+Space | 添加到多列排序 |
| Ctrl+Click | 行头列头多选(不连续) |
| Shift+Click | 行头列头范围选择(连续) |
- 展开折叠
| 快捷键 | 功能 |
|---|---|
| + / - | 展开 / 折叠当前节点 |
| Ctrl+Shift+Enter | 全部展开 |
| Ctrl+Shift+Backspace | 全部折叠 |
- 其他
| 快捷键 | 功能 |
|---|---|
| Ctrl+Z | 撤销编辑 |
| F5 | 刷新数据 |
| Shift+F10 | 打开右键菜单 |
| Alt+↓ / F4 | 打开下拉框 / 筛选弹窗 |
| Ctrl+F11 | 切换全屏(系统级) |
皮肤与外观
系统支持多种 DevExpress 皮肤主题:
切换皮肤
点击顶部工具栏的皮肤选择器
从下拉列表中选择喜欢的皮肤
整个界面立即生效
推荐皮肤
| 皮肤 | 风格 |
|---|---|
| Office 2019 Colorful | 现代、明亮 |
| The Bezier | 圆润、柔和 |
| Dark | 深色护眼 |
| Office 2016 Colorful | 经典 Office 风格 |
主框架页面
- ToolbarFormControl(顶部工具栏)
顶部工具栏包含以下区域:
左侧区域:
| 按钮 | 功能 | 快捷键 |
|---|---|---|
| 业务 | 业务功能菜单(动态生成) | — |
| 系统 | 系统管理菜单 | — |
| 语言 | 切换界面语言(中文/英文) | — |
| 皮肤 | 切换界面皮肤主题 | — |
右侧区域:
| 按钮 | 功能 | 快捷键 |
|---|---|---|
| 用户信息 | 显示当前登录用户名 | — |
| 重新登录 | 退出当前会话并重新登录 | Alt+R |
| 修改密码 | 打开修改密码对话框 | — |
| 关闭所有 | 关闭所有打开的页面 | — |
| 通知 | 查看系统通知消息 | — |
| 广播 | 发送广播消息(仅管理员) | — |
| 帮助 | 打开帮助文档 | — |
| 关于 | 关于系统信息 | — |
顶部工具栏显示/隐藏:
按 Ctrl+F11 全屏时,工具栏自动隐藏
再次按 Ctrl+F11 恢复显示
侧边栏与分割条
侧边栏 :包含导航菜单树的 PanelControl 容器
分割条(Splitter) :拖动可调整侧边栏宽度
显示/隐藏侧边栏 :点击工具栏的侧边栏按钮
操作技巧:
拖动分割条可以调整侧边栏宽度
侧边栏可以完全隐藏以获得更大的工作区
隐藏后通过工具栏按钮恢复显示
状态栏
底部状态栏显示:
| 区域 | 说明 |
|---|---|
| 消息区 | 显示操作结果提示(如"保存成功") |
| 本机IP | 显示客户端IP地址 |
| 系统时间 | 实时显示当前日期和时间(每秒更新) |
| 班次信息 | 显示当前班次信息 |
- 窗体生命周期
所有业务页面以 MDI(多文档界面)方式管理:
用户双击导航菜单
→ DynamicLoad(menuItem)
→ GetFormInstanceFromMenuResource(menuItem) // 通过反射创建窗体实例
→ 检查是否已打开相同页面
├─ 已打开 → 激活该页面(Activate)
└─ 未打开 → 设置 MdiParent → Show()
→ TabbedView 自动添加标签页页面去重: 如果同一页面已打开,不会重复创建,而是直接切换到该标签页。
关闭页面: 关闭标签页时,窗体自动释放资源,TabbedView 移除对应标签。
常见操作场景
- 场景一:批量选择并导出
- 在表格中勾选需要的行(点击行首 checkbox)
- 右键 → 导出到 Excel
- 选择保存路径
- 场景二:快速查找某条记录
- 按 Ctrl+F 打开查找面板
- 输入关键词(如订单号、名称)
- 表格自动过滤显示匹配结果
- 按 F3 跳转到下一个匹配项
- 场景三:自定义列显示
- 右键列头 → 列自定义
- 在弹出面板中拖动列来调整顺序
- 勾选/取消勾选来显示/隐藏列
- 拖动列头分隔线调整列宽
- 关闭面板后,布局自动保存
- 场景四:按条件筛选数据
- 点击目标列头的漏斗图标
- 选择"自定义筛选"
- 设置筛选条件(如"大于 100"、"包含关键词")
- 确认后表格自动过滤
- 场景五:多级分组查看
- 将"部门"列头拖到上方分组面板
- 再将"日期"列头拖到分组面板
- 数据按"部门 → 日期"两级分组显示
- 点击分组行的 + 展开查看明细
- 场景六:查看行详情
- 右键点击目标行
- 选择"表单查看"
- 弹出详情卡片,显示该行所有字段
发布与部署
我们的winform前端是随着后端服务一起发布和部署的,您可以参见之前的章节。