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)
    // ... 原有组件声明
  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 实现IsMaterialExistsAddMaterialToRK 提供具体业务逻辑
5. 设置回调连接 FrmRK FM.Callback := Self 建立两个对象的联系
6. 触发接口调用 FMTop20Record 在双击事件中调用FCallback.xxx 执行回调操作

实际贴合说明

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

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

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

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

这种模式的优势:

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

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

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

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

posted @ 2025-12-28 11:30  一曲轻扬  阅读(10)  评论(0)    收藏  举报