ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Delphi 窗体间通信之接口回调模式

Delphi 窗体间通信之接口回调模式

 

Delphi 窗体间通信之事件回调模式 - 一曲轻扬 - 博客园 (cnblogs.com)

书接上例.这回我们将使用接口回调模式,来完成窗体间的通讯问题

核心套路:

  1. 定义接口(在弹出窗体单元)

  2. 实现接口(在主窗体/框架单元)

  3. 设置回调(创建时连接)

  4. 触发回调(事件发生时)


具体实现代码如下:

第一步:在 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)// ... 原有组件声明privateFCallback: IMaterialSelectCallback;  // 存储接口引用public// 设置回调接口的方法procedure SetCallback(ACallback: IMaterialSelectCallback);property Callback: IMaterialSelectCallback read FCallback write SetCallback;// ... 原有方法声明end;// ... 原有实现部分// 2. 实现设置回调接口的方法
procedure TTop20Record.SetCallback(ACallback: IMaterialSelectCallback);
beginFCallback := ACallback;  // 保存主框架传递来的接口实例
end;// 3. 修改双击事件,通过接口回调
procedure TTop20Record.GridViewTop20CellDblClick(Sender: TcxCustomGridTableView;ACellViewInfo: TcxGridTableDataCellViewInfo; AButton: TMouseButton;AShift: TShiftState; var AHandled: Boolean);
varCurrentMaterialID: Integer;
beginAHandled := True;  // 阻止事件继续传播// 安全检查:确保有数据和回调接口if FDQTop20.IsEmpty or not Assigned(FCallback) thenExit;// 获取当前记录的物料ID(关键业务字段)CurrentMaterialID := FDQTop20.FieldByName('物料ID').AsInteger;// 4. 第一次接口调用:检查是否已存在if FCallback.IsMaterialExists(CurrentMaterialID) thenbegin// 业务逻辑:已存在时询问用户if MessageDlg('该物料已存在于入库单中,是否继续添加?',mtConfirmation, [mbYes, mbNo], 0) = mrNo thenExit;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;interfaceuses// ... 原有uses部分FMTop20Record,  // 引用窗体单元(这里不会造成循环,因为只是使用)// ... 其他uses
  System.Generics.Collections;typeTRK = 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;
beginResult := False;// 遍历FDMemTable1(入库单临时表)查找相同物料IDFDMemTable1.DisableControls;  // 暂停UI刷新提高性能tryFDMemTable1.First;while not FDMemTable1.EOF dobeginif FDMemTable1.FieldByName('物料ID').AsInteger = MaterialID thenbeginResult := True;  // 找到相同物料ID
        Break;end;FDMemTable1.Next;end;finallyFDMemTable1.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;  // 对应仓库IDFDMemTable1.FieldByName('数量').AsFloat := 1.0;  // 默认数量FDMemTable1.FieldByName('备注').Clear;          // 清空备注
    FDMemTable1.Post;  // 提交记录// 用户反馈(可选)ShowMessage(Format('"%s"已添加到入库单', [MaterialName]));exceptFDMemTable1.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 thenrkobj := Format('部门ID=%d', [入库对象.Tag])elserkobj := 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;finallyFM.Free;end;
end;

 

套路总结表

 
步骤所在单元关键操作目的
1. 定义接口 FMTop20Record 定义IMaterialSelectCallback 建立通信契约
2. 添加接口属性 FMTop20Record 添加FCallback字段和Callback属性 存储接口引用
3. 声明实现接口 FrmRK TRK = class(TFrame, IMaterialSelectCallback) 表明实现接口的能力
4. 实现接口方法 FrmRK 实现IsMaterialExistsAddMaterialToRK 提供具体业务逻辑
5. 设置回调连接 FrmRK FM.Callback := Self 建立两个对象的联系
6. 触发接口调用 FMTop20Record 在双击事件中调用FCallback.xxx 执行回调操作

实际贴合说明

  1. 字段对应:接口方法参数与FDQTop20查询字段完全对应

  2. 业务逻辑:检查重复、添加记录都使用实际的FDMemTable1

  3. 原有代码:保持原有的SQL构建逻辑和验证逻辑不变

  4. 数据结构:WarehouseID对应收货仓库字段,MaterialID是核心关联字段

这种模式的优势:

  • 解耦:两个单元不直接依赖对方的具体实现

  • 可测试:可以创建模拟对象测试接口

  • 可扩展:其他窗体也可实现同一接口

  • 类型安全:接口提供编译时类型检查

返回列表