Skip to content

前端winform开发

新增页面

在你的前端项目,比如Hello.Wnform里面新建一个Forms文件夹,我们一般喜欢把页面放到这个文件夹里面。

按照开发规范,我们建议每个页面的名称如: FrmPA1000

参考下面的项目文件结构:

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

页面的主要逻辑的代码也很简单:(你的页面必须继承 HmxBaseForm

csharp
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框架是完全前后端分离的,所以你在测试的时候要确保后端服务已经启动,或者同时启动。

我们一般这么设置:

这样你就可以很好的同时运行和调试前端和后端了。

常见组件使用

消息提示框

csharp
// 基本提示
MsgBox.Show("操作成功");
MsgBox.ShowWarning("请注意");
MsgBox.ShowError("出错了");
MsgBox.ShowYesNo("确认删除?"); // 返回 DialogResult

// Toast 风格提示(自动消失)
MsgBox.ShowAlertSuccess("保存成功");
MsgBox.ShowAlertFailure("保存失败");
MsgBox.ShowAlert("提示信息");

// 显示异常(自动截图 + 写日志)
MsgBox.ShowError(ex);

// 状态栏消息
MsgBox.ShowStatus("已选择 3 行");

消息等待框

csharp
// using 语法,自动关闭
using var wait = new WaitDialog("正在加载...");
await SomeAsyncOperation();
// wait dispose 时自动关闭对话框

自动控件管线

ViewControl 是框架的自动控件初始化管线——窗体上放的 DevExpress 控件无需手动配置,框架在 HandleCreated 时自动发现并执行对应的 Executor。

工作原理:

text
窗体构造
└─ new ViewControl(this) // 持有当前 Form 引用

HandleCreated 触发
└─ LoadComponents() // 反射收集所有 Component 字段
└─ Start()
├─ 同步 Executor // AsyncRun = false 的先执行
└─ 异步 Executor // AsyncRun = true 的后执行
└─ 逐个 Executor.Excute(component, options)

执行流程:

  1. 框架用反射获取窗体上所有 Component 类型的字段(继承链上直到 HmxBaseForm 为止)
  2. 对每个 Component,查找已注册的 IViewControlExecutor,匹配规则是 component.GetType().HasParentType(executor.CompnentType)
  3. 调用 executor.Excute(component, options) 执行自动配置
  4. NoCompnentViewControlExecutor 的子类不绑定特定控件,对整个 Form 执行(如多语言、GlueForm 生命周期管理)
Executor绑定类型同步/异步自动完成的事
ViewControlExecutor_GridControlGridControl异步行号绘制、右键菜单、checkbox 全选、选中行高亮、布局保存、鼠标滚轮关闭编辑器、回车跳下一列、变更状态颜色
ViewControlExecutor_GridControl_PopupMenuGridControl同步右键弹出菜单(复制/导出/打印/统计等)
ViewControlExecutor_TreeListTreeList异步多选、右键菜单、checkbox 修复、布局保存
ViewControlExecutor_BaseEditBaseEdit 及子类异步ComboBox 下拉行数、AllowNullInput 清空按钮、DateEdit TodayClick、SearchLookUpEdit 自动列宽、F3 配置数据字典、多语言翻译下拉项、验证规则
ViewControlExecutor_SimpleButtonSimpleButton同步事件拦截(防止重复点击)、按钮权限校验(无权限自动隐藏)
ViewControlExecutor_LayoutControlLayoutControl异步自动计算高度、布局保存、右键菜单
ViewControlExecutor_Localization无(NoComponent)自动多语言翻译(按钮/列头/标签/布局项等)
ViewControlExecutor_GlueForm无(NoComponent)Form 关闭时自动停 Timer、Dispose 时清空所有字段和事件(防内存泄漏)

在窗体构造函数或 Load 中,通过 ViewControl.For(component) 获取并修改 Options:

csharp
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;
}
属性默认值说明
AllowColumnAutoWidthfalse列自动宽度
AllowMultipleCheckboxtrue列头 checkbox 全选
AllowGridviewDrawRowIndicatortrue行号绘制
AllowGridviewDrawRowCelltrue选中行高亮
AllowSummaryInfoInStatusBartrue状态栏显示选中统计
AllowSaveLayoutToXmlfalse布局保存(右键菜单可切换)
AllowSyncRowStateToCheckboxSelectionfalse行选择状态同步到 checkbox
UseCellMergeWithPreColumnfalse按前列合并单元格

数据门户请求器

dpc 在 HmxBaseForm 中已初始化,直接用:

csharp
// 调用远程服务
var result = await dpc.Proxy<IMyService>().GetDataAsync();
// 带参数
var result = await dpc.Proxy<IMyService>().GetByIdAsync(id);

多语言服务

csharp
// 在代码中
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 与系统配置的按钮编码一致,框架自动处理。

控件扩展方法

csharp
// 线程安全更新 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 图标

csharp
// 加载单个 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 设为字典编码,框架自动从服务器拉取数据:

csharp
// 设计器中:设置 ComboBoxEdit(下拉控件一般都有) 的 AccessibleName = "ORDER_STATUS"
// 运行时自动绑定数据字典
// 管理员按 F3 可打开字典维护窗口

数据变更追踪与保存

csharp
// 初始化可追踪列表
bindingSource1.InitializeObservableList<OrderDto>();

// 或从服务器获取后包装
bindingSource1.DataSource = data.AsTrackable();

// 保存(自动识别增/删/改)
await bindingSource1.SaveChanges<OrderDto>();

// 获取原始数据(无追踪)
var original = bindingSource1.GetNoTrackingList<OrderDto>();

设计器扩展属性

HmxControlExtenderProvider 提供一组设计器属性,在 Visual Studio 属性窗口中可用:

扩展属性目标控件说明
AutoHmxStyleSimpleButton自动设置按钮基础样式
IconifyIconSimpleButton格式 icon-[ph--acorn],参考 yesicon.app
AutoGenerateButtonsStackPanel自动生成按钮布局
AutoGenerateColumnCaptionGridControl根据实体 DisplayName 自动设置列头
AutoGenerateItemTextLayoutControlItem根据实体 DisplayName 自动设置布局项文本
EnableLocalization所有 Component是否允许自动多语言翻译(默认 true)
TemplateDataSpreadsheetControl保存/恢复电子表格模板

配置文件

前端WinForm使用 appsettings.json 进行配置,主要配置与后端服务的连接信息。

配置文件位置

项目配置文件路径
前端WinFormadmin/Hmx.WinForms.Startup/appsettings.json

完整配置结构

json
{
  "AppSettings": {
    "loglevel": "debug"
  },
  "ConnectionStrings": {
    "SignalR": "http://192.168.132.68:5225",
    "dataPortalUrl": "http://127.0.0.1:5225/api"
  },
  "AllowedHosts": "*"
}

配置项详解

1. AppSettings - 应用设置

配置项类型默认值说明
loglevelstringdebug日志级别:Trace, Debug, Info, Warn, Error, Fatal

2. ConnectionStrings - 连接字符串

配置项必填说明
SignalR后端服务的SignalR地址,用于实时通信
dataPortalUrl后端服务的数据访问地址,支持HTTP和TCP两种模式

dataPortalUrl 连接格式:

模式协议格式示例说明
HTTPhttphttp://{host}:{port}/apihttp://192.168.1.100:5225/api标准HTTP访问,适合大多数场景
TCPtcptcp://{host}:{port}/tcp://192.168.1.100:9026/高性能TCP访问,适合大数据量场景

带超时参数的连接格式:

http://{host}:{port}/api?timeout=120
tcp://{host}:{port}/?timeout=120
参数类型默认值说明
timeoutint600请求超时时间(秒),默认10分钟

注意:框架会根据URL前缀自动识别连接模式(Auto模式)。

代码中读取配置:

csharp
// 读取SignalR地址
var signalRUrl = EngineContext.Current.Resolve<IConfiguration>()
    .GetSection("ConnectionStrings:SignalR").Get<string>();

// 读取数据访问地址
var dataPortalUrl = DataPortalClientOptionsForServiceClient.DataPortalUrl;

配置示例:

json
{
  "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(多文档界面)方式管理:

text
用户双击导航菜单
 → DynamicLoad(menuItem)
 → GetFormInstanceFromMenuResource(menuItem)  // 通过反射创建窗体实例
 → 检查是否已打开相同页面
    ├─ 已打开 → 激活该页面(Activate)
    └─ 未打开 → 设置 MdiParent → Show()
 → TabbedView 自动添加标签页

页面去重: 如果同一页面已打开,不会重复创建,而是直接切换到该标签页。

关闭页面: 关闭标签页时,窗体自动释放资源,TabbedView 移除对应标签。

常见操作场景

  • 场景一:批量选择并导出
  1. 在表格中勾选需要的行(点击行首 checkbox)
  2. 右键 → 导出到 Excel
  3. 选择保存路径
  • 场景二:快速查找某条记录
  1. 按 Ctrl+F 打开查找面板
  2. 输入关键词(如订单号、名称)
  3. 表格自动过滤显示匹配结果
  4. 按 F3 跳转到下一个匹配项
  • 场景三:自定义列显示
  1. 右键列头 → 列自定义
  2. 在弹出面板中拖动列来调整顺序
  3. 勾选/取消勾选来显示/隐藏列
  4. 拖动列头分隔线调整列宽
  5. 关闭面板后,布局自动保存
  • 场景四:按条件筛选数据
  1. 点击目标列头的漏斗图标
  2. 选择"自定义筛选"
  3. 设置筛选条件(如"大于 100"、"包含关键词")
  4. 确认后表格自动过滤
  • 场景五:多级分组查看
  1. 将"部门"列头拖到上方分组面板
  2. 再将"日期"列头拖到分组面板
  3. 数据按"部门 → 日期"两级分组显示
  4. 点击分组行的 + 展开查看明细
  • 场景六:查看行详情
  1. 右键点击目标行
  2. 选择"表单查看"
  3. 弹出详情卡片,显示该行所有字段

发布与部署

我们的winform前端是随着后端服务一起发布和部署的,您可以参见之前的章节。

HiMind 工业互联网平台 技术文档