Delphi 窗体间通信之接口回调模式
Delphi 窗体间通信之事件回调模式 - 一曲轻扬 - 博客园 (cnblogs.com)
书接上例.这回我们将使用接口回调模式,来完成窗体间的通讯问题
核心套路:
-
定义接口(在弹出窗体单元)
-
实现接口(在主窗体/框架单元)
-
设置回调(创建时连接)
-
触发回调(事件发生时)
具体实现代码如下:
第一步:在 FMTop20Record 单元定义接口
// ==================== FMTop20Record.pas ==================== unit FMTop20Record; interface // ... 原有uses部分保持不变 type // 1. 定义物料选择回调接口 // GUID使用Ctrl+Shift+G生成,确保唯一性 IMaterialSelectCallback = interface ['{4C9F8A12-7B2C-4D8B-89A3-6F1B7E5D9F0A}'] // 方法1:检查物料是否已存在(用于提示用户) function IsMaterialExists(MaterialID: Integer): Boolean; // 方法2:添加物料到入库单(核心业务逻辑) procedure AddMaterialToRK(MaterialID: Integer; MaterialCode, MaterialName, SpecModel, CustomerPartNo: string; WarehouseID: Integer); end; TTop20Record = class(TForm) // ... 原有组件声明 private FCallback: IMaterialSelectCallback; // 存储接口引用 public // 设置回调接口的方法 procedure SetCallback(ACallback: IMaterialSelectCallback); property Callback: IMaterialSelectCallback read FCallback write SetCallback; // ... 原有方法声明 end; // ... 原有实现部分 // 2. 实现设置回调接口的方法 procedure TTop20Record.SetCallback(ACallback: IMaterialSelectCallback); begin FCallback := ACallback; // 保存主框架传递来的接口实例 end; // 3. 修改双击事件,通过接口回调 procedure TTop20Record.GridViewTop20CellDblClick( Sender: TcxCustomGridTableView; ACellViewInfo: TcxGridTableDataCellViewInfo; AButton: TMouseButton; AShift: TShiftState; var AHandled: Boolean); var CurrentMaterialID: Integer; begin AHandled := True; // 阻止事件继续传播 // 安全检查:确保有数据和回调接口 if FDQTop20.IsEmpty or not Assigned(FCallback) then Exit; // 获取当前记录的物料ID(关键业务字段) CurrentMaterialID := FDQTop20.FieldByName('物料ID').AsInteger; // 4. 第一次接口调用:检查是否已存在 if FCallback.IsMaterialExists(CurrentMaterialID) then begin // 业务逻辑:已存在时询问用户 if MessageDlg('该物料已存在于入库单中,是否继续添加?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then Exit; end; // 5. 第二次接口调用:执行添加操作 FCallback.AddMaterialToRK( CurrentMaterialID, FDQTop20.FieldByName('物料代码').AsString, // 物料代码 FDQTop20.FieldByName('物料名称').AsString, // 物料名称 FDQTop20.FieldByName('规格型号').AsString, // 规格型号 FDQTop20.FieldByName('客户料号').AsString, // 客户料号 FDQTop20.FieldByName('仓库ID').AsInteger // 仓库ID(用于收货仓库字段) ); end;
第二步:在 FrmRK 单元实现接口
// ==================== FrmRK.pas ==================== unit FrmRK; interface uses // ... 原有uses部分 FMTop20Record, // 引用窗体单元(这里不会造成循环,因为只是使用) // ... 其他uses System.Generics.Collections; type TRK = class(TFrame, IMaterialSelectCallback) // 关键:声明实现接口 private // IMaterialSelectCallback 接口实现方法 function IsMaterialExists(MaterialID: Integer): Boolean; procedure AddMaterialToRK(MaterialID: Integer; MaterialCode, MaterialName, SpecModel, CustomerPartNo: string; WarehouseID: Integer); public // ... 原有方法和属性 end; implementation // 6. 实现接口方法1:检查物料是否存在 function TRK.IsMaterialExists(MaterialID: Integer): Boolean; begin Result := False; // 遍历FDMemTable1(入库单临时表)查找相同物料ID FDMemTable1.DisableControls; // 暂停UI刷新提高性能 try FDMemTable1.First; while not FDMemTable1.EOF do begin if FDMemTable1.FieldByName('物料ID').AsInteger = MaterialID then begin Result := True; // 找到相同物料ID Break; end; FDMemTable1.Next; end; finally FDMemTable1.EnableControls; // 恢复UI刷新 end; end; // 7. 实现接口方法2:添加物料到入库单 procedure TRK.AddMaterialToRK(MaterialID: Integer; MaterialCode, MaterialName, SpecModel, CustomerPartNo: string; WarehouseID: Integer); begin // 在FDMemTable1中添加新记录 FDMemTable1.Append; try // 设置各个字段值(与FMTop20Record中的查询字段对应) FDMemTable1.FieldByName('物料ID').AsInteger := MaterialID; FDMemTable1.FieldByName('物料代码').AsString := MaterialCode; FDMemTable1.FieldByName('物料名称').AsString := MaterialName; FDMemTable1.FieldByName('规格型号').AsString := SpecModel; FDMemTable1.FieldByName('客户料号').AsString := CustomerPartNo; FDMemTable1.FieldByName('收货仓库').AsInteger := WarehouseID; // 对应仓库ID FDMemTable1.FieldByName('数量').AsFloat := 1.0; // 默认数量 FDMemTable1.FieldByName('备注').Clear; // 清空备注 FDMemTable1.Post; // 提交记录 // 用户反馈(可选) ShowMessage(Format('"%s"已添加到入库单', [MaterialName])); except FDMemTable1.Cancel; // 发生异常时取消操作 raise; // 重新抛出异常 end; end; // 8. 修改原有方法,设置接口回调 procedure TRK.入库单号RightButtonClick(Sender: TObject); var rkobj: string; FM: TTop20Record; begin // ... 原有验证逻辑(检查入库对象等) // 创建Top20记录窗体 FM := TTop20Record.Create(nil); try // 设置窗体属性(原有逻辑) FM.sobj := 入库对象.Text; // 构建SQL(原有逻辑) if SCLKRaBtn.Checked then rkobj := Format('部门ID=%d', [入库对象.Tag]) else rkobj := Format('供应商ID=%d', [入库对象.Tag]); FM.isql := 'SELECT DISTINCT top 20 入库单.日期, 入库单.物料ID, ' + '物料代码, 物料名称, 规格型号, 客户料号, 助记码, ' + '物料信息.材质, 仓库列表.仓库ID, 仓库列表.仓库名称 ' + 'FROM (入库单 LEFT JOIN 物料信息 ON 入库单.物料ID = 物料信息.物料ID) ' + 'LEFT JOIN 仓库列表 ON 物料信息.仓库ID = 仓库列表.仓库ID ' + 'WHERE ' + rkobj; // 9. 关键连接:将自身(实现接口的TRK实例)设置为回调 FM.Callback := Self; // Self就是TRK的当前实例,它实现了IMaterialSelectCallback // 加载数据并显示窗体 FM.GetData; FM.ShowModal; finally FM.Free; end; end;
套路总结表
| 步骤 | 所在单元 | 关键操作 | 目的 |
|---|---|---|---|
| 1. 定义接口 | FMTop20Record | 定义IMaterialSelectCallback |
建立通信契约 |
| 2. 添加接口属性 | FMTop20Record | 添加FCallback字段和Callback属性 |
存储接口引用 |
| 3. 声明实现接口 | FrmRK | TRK = class(TFrame, IMaterialSelectCallback) |
表明实现接口的能力 |
| 4. 实现接口方法 | FrmRK | 实现IsMaterialExists和AddMaterialToRK |
提供具体业务逻辑 |
| 5. 设置回调连接 | FrmRK | FM.Callback := Self |
建立两个对象的联系 |
| 6. 触发接口调用 | FMTop20Record | 在双击事件中调用FCallback.xxx |
执行回调操作 |
实际贴合说明
-
字段对应:接口方法参数与
FDQTop20查询字段完全对应 -
业务逻辑:检查重复、添加记录都使用实际的
FDMemTable1 -
原有代码:保持原有的SQL构建逻辑和验证逻辑不变
-
数据结构:
WarehouseID对应收货仓库字段,MaterialID是核心关联字段
这种模式的优势:
-
解耦:两个单元不直接依赖对方的具体实现
-
可测试:可以创建模拟对象测试接口
-
可扩展:其他窗体也可实现同一接口
-
类型安全:接口提供编译时类型检查

浙公网安备 33010602011771号