Delphi 窗体间通信之事件回调模式
Delphi 窗体间数据传递的「事件回调模式」套路总结
一、核心思想
「单向依赖 + 事件驱动」 - 下级窗体通过事件通知上级,避免双向引用导致的循环依赖。
在下级窗体中完成参数收集工作,并传向上级窗体.
在上级窗体关联并具现事件方法.
以下以FrmRK单元(上级)和FMTop20Record (下级)为例,演示实现过程
二、标准套路(5步法)
第1步:定义事件类型(在下级单元)
// 在 FMTop20Record 单元的 interface 部分添加 type // 定义物料选择事件类型.自定义事件要定义在窗体之前,因为窗体要引用这个自定义事件 TMaterialSelectEvent = procedure(Sender: TObject; MaterialID: Integer; MaterialCode, MaterialName, SpecModel, CustomerPartNo: string; WarehouseID: Integer) of object;
第2步:暴露事件属性(在下级类)
TTop20Record = class(TForm) //注意窗体和事件的定义顺序 // ... 原有代码 private FOnMaterialSelected: TMaterialSelectEvent; // 物料选择事件 published // 物料选择事件属性 property OnMaterialSelected: TMaterialSelectEvent read FOnMaterialSelected write FOnMaterialSelected; end;
第3步:触发事件(在下级适当位置)
这是cxGrid的一个OnCellDblClick事件,即单元格的双击事件,它把用户双击的单元格的数据收集起来,并传给上级
procedure TTop20Record.GridViewTop20CellDblClick(Sender: TcxCustomGridTableView; ACellViewInfo: TcxGridTableDataCellViewInfo; AButton: TMouseButton; AShift: TShiftState; var AHandled: Boolean); begin AHandled := True; // 标记事件已处理 if FDQTop20.IsEmpty then Exit; // 数据集为空则退出 // 检查是否有事件处理程序 if Assigned(FOnMaterialSelected) then begin // 触发物料选择事件,传递当前行的数据 FOnMaterialSelected(Self, FDQTop20.FieldByName('物料ID').AsInteger, FDQTop20.FieldByName('物料代码').AsString, FDQTop20.FieldByName('物料名称').AsString, FDQTop20.FieldByName('规格型号').AsString, FDQTop20.FieldByName('客户料号').AsString, FDQTop20.FieldByName('仓库ID').AsInteger); end; end;
第4步:实现事件处理(在上级单元)
procedure TRK.HandleMaterialSelected(Sender: TObject; MaterialID: Integer; MaterialCode, MaterialName, SpecModel, CustomerPartNo: string; WarehouseID: Integer); var Found: Boolean; begin // 1. 检查是否已存在相同的物料ID Found := False; FDMemTable1.DisableControls; // 禁用控件更新以提高性能 try FDMemTable1.First; while not FDMemTable1.EOF do begin if FDMemTable1.FieldByName('物料ID').AsInteger = MaterialID then begin Found := True; Break; end; FDMemTable1.Next; end; finally FDMemTable1.EnableControls; // 恢复控件更新 end; // 2. 如果已存在,提示用户 if Found then begin if MessageDlg('该物料已存在于入库单中,是否继续添加?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then Exit; end; // 3. 添加新记录到FDMemTable1 FDMemTable1.Append; try FDMemTable1.FieldByName('物料ID').AsInteger := MaterialID; FDMemTable1.FieldByName('物料代码').AsString := MaterialCode; FDMemTable1.FieldByName('物料名称').AsString := MaterialName; FDMemTable1.FieldByName('规格型号').AsString := SpecModel; FDMemTable1.FieldByName('客户料号').AsString := CustomerPartNo; FDMemTable1.FieldByName('数量').AsFloat := 1.0; // 默认数量 FDMemTable1.FieldByName('备注').AsString := ''; // 空备注 FDMemTable1.FieldByName('收货仓库').AsInteger := WarehouseID; FDMemTable1.Post; // 提交记录 except FDMemTable1.Cancel; // 出错时取消添加 raise; // 重新抛出异常 end; end;
第5步:关联事件(创建下级窗体时,也就是showModal前)
procedure TRK.入库单号RightButtonClick(Sender: TObject);begin // ... 原有的检查代码 ... var FM := TTop20Record.Create(nil); try var FM.sobj := 入库对象.Text;// 关联事件处理程序.关键步骤 FM.OnMaterialSelected := HandleMaterialSelected; FM.GetData; FM.ShowModal; finally FM.Free; end; end;
三、设计模式要点
1. 依赖方向
父窗体 (FrmRK) → 使用 → 子窗体 (FMTop20Record)
↑ ↓
|--(事件回调)------------|
-
编译时依赖:父→子(uses 子单元)
-
运行时通信:子→父(通过事件)
2. 松耦合设计
-
子窗体不知道谁处理事件
-
父窗体不知道子窗体内部实现
-
仅通过事件接口交互
3. 扩展性优势
-
同一子窗体可被多个父窗体复用
-
只需实现不同的事件处理程序
-
添加新参数只需修改事件类型定义
四、使用场景判断
适合使用此模式:
-
父窗体打开子窗体选择/查询数据
-
需要将子窗体的结果传回父窗体
-
避免窗体间的循环引用
-
希望子窗体可被多处复用
不适合此模式:
-
简单的消息显示(用 ShowMessage)
-
单向数据展示(用属性传递)
-
复杂的主从窗体(用数据模块)
五、常见变体
变体1:多事件支持
// 子窗体可定义多个事件 property OnSelect: TSelectEvent; property OnCancel: TCancelEvent; property OnClose: TCloseEvent;
变体2:模态结果增强
// 结合 ModalResult 和事件 if Assigned(FOnDataSelected) then begin FOnDataSelected(Self, Data); ModalResult := mrOK; // 自动关闭 end;
变体3:带返回值的事件
// 事件处理程序可返回布尔值 TValidationEvent = function(Sender: TObject; Data: TData): Boolean of object;
六、对比其他方案
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 事件回调 | 松耦合、可复用、类型安全 | 稍复杂 | 窗体间数据传递 |
| 接口方式 | 更灵活、支持多接口 | 更复杂、需定义GUID | 复杂交互系统 |
| 全局变量 | 简单直接 | 紧耦合、难维护 | 简单原型 |
| 消息机制 | 完全解耦 | 类型不安全、调试困难 | 跨进程/复杂框架 |
七、最佳实践
-
命名规范
-
事件类型:
T[动作]Event -
事件属性:
On[动作] -
处理方法:
Handle[动作]
-
-
参数设计
-
包含
Sender: TObject参数 -
按需传递最少必要数据
-
避免传递大型对象(传ID而非对象)
-
-
错误处理
-
始终检查
Assigned(FOnXxx) -
在事件处理器中做好异常处理
-
考虑添加取消机制
-
八、记忆口诀
「一下二暴三触发,四实五联搞定它」
一下:下级定义事件类型
二暴:暴露事件属性
三触发:适当时机触发事件
四实:上级实现处理方法
五联:创建时关联事件
这个套路是 Delphi 窗体间通信的经典解决方案,掌握后可以应对80%的窗体数据传递需求,既避免了循环引用,又保持了代码的清晰和可维护性。


浙公网安备 33010602011771号