書接上例.這回我們將使用接口回調模式,來完成窗體間的通訊問題
核心套路:
- 定義接口(在彈出窗體單元)
- 實現接口(在主窗體/框架單元)
- 設置回調(創建時連接)
- 觸發回調(事件發生時)
具體實現代碼如下:
第一步:在 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
|
定義 |
建立通信契約
|
|
2. 添加接口屬性
|
FMTop20Record
|
添加 |
存儲接口引用
|
|
3. 聲明實現接口
|
FrmRK
|
|
表明實現接口的能力
|
|
4. 實現接口方法
|
FrmRK
|
實現 |
提供具體業務邏輯
|
|
5. 設置回調連接
|
FrmRK
|
|
建立兩個對象的聯繫
|
|
6. 觸發接口調用
|
FMTop20Record
|
在雙擊事件中調用 |
執行回調操作
|
實際貼合説明
- 字段對應:接口方法參數與
FDQTop20查詢字段完全對應 - 業務邏輯:檢查重複、添加記錄都使用實際的
FDMemTable1 - 原有代碼:保持原有的SQL構建邏輯和驗證邏輯不變
- 數據結構:
WarehouseID對應收貨倉庫字段,MaterialID是核心關聯字段
這種模式的優勢:
- 解耦:兩個單元不直接依賴對方的具體實現
- 可測試:可以創建模擬對象測試接口
- 可擴展:其他窗體也可實現同一接口
- 類型安全:接口提供編譯時類型檢查