書接上例.這回我們將使用接口回調模式,來完成窗體間的通訊問題

核心套路:

  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是核心關聯字段

這種模式的優勢:

  • 解耦:兩個單元不直接依賴對方的具體實現
  • 可測試:可以創建模擬對象測試接口
  • 可擴展:其他窗體也可實現同一接口
  • 類型安全:接口提供編譯時類型檢查