在 Business Central AL 中實作 Lookup to another table(建立查詢選取視窗)

延伸文章 建立一個在 Business Central 裡獨立運行的應用程式(CRUD) 中的運用,在 Name 欄位 → 開窗查詢 User 資料(例如從系統 User 表或自訂表)→ 選取後自動把使用者的名稱與描述帶回當前記錄。

整體概念

在 AL Page 裡可以使用:

  • TableRelation → 建立 Lookup 關聯
  • OnValidate()OnLookup() → 自訂選取邏輯與資料回填

可以用「系統內建的 User 表」(User table, ID = 2000000120) 作為範例。

BasicTablePage.al

C
page 60100 "Basic Table Page"
{
    PageType = List;
    ApplicationArea = All;
    SourceTable = "Basic Table";
    UsageCategory = Lists; // 讓它可以從搜尋直接找到

    layout
    {
        area(content)
        {
            repeater(Group)
            {
                field(ID; Rec."ID")
                {
                    ApplicationArea = All;
                    Editable = false;
                }
                field(Name; Rec."Name")
                {
                    ApplicationArea = All;
                }
                field(Description; Rec."Description")
                {
                    ApplicationArea = All;
                }
                field("User ID"; Rec."User ID")
                {
                    ApplicationArea = All;
                    Caption = 'User';
                    TableRelation = User."User Security ID"; // 建立查詢來源
                    trigger OnLookup(var Text: Text): Boolean
                    var
                        UserRec: Record User;
                    begin
                        // 開啟使用者清單供選擇
                        if PAGE.RunModal(PAGE::"Users", UserRec) = ACTION::LookupOK then begin
                            Rec."User ID" := UserRec."User Security ID";
                            Rec."Name" := UserRec."User Name";
                            Rec."Description" := UserRec."Full Name";
                        end;
                        exit(true); // 告訴系統這個 lookup 已被處理
                    end;
                }
            }
        }
    }
}

功能說明

  1. 使用者點擊 User ID 欄位 → 系統開啟內建的 Users 頁面。
  2. 選取使用者 → 回傳資料到變數 UserRec
  3. UserRec 的欄位值(User Name, Full Name)帶回當前記錄:
C
Rec."Name" := UserRec."User Name";
Rec."Description" := UserRec."Full Name";

延伸應用

若你想用自訂的 User 表(例如 EmployeeCustomer),只要:

  • UserRec 改成你的資料表記錄變數;
  • PAGE.RunModal(PAGE::"Users", UserRec) 改成對應的頁面名稱;
  • 調整回填欄位即可。

總結

這樣部署後:

  • Hello World Page 點選 User ID → 開窗顯示使用者清單
  • 選擇一位使用者 → 自動帶入「使用者名稱」與「全名」到 NameDescription 欄位

使用既有欄位查詢

新增一個欄位,似乎是多此一舉,改成「使用者點擊 Name 欄位 → 開啟系統內建的 Users 頁面 → 選取使用者 → 自動回填 Name 與 Description 欄位」而且 不增加 User ID 欄位

解法重點

在 AL 中可以用:

  • trigger OnLookup() 自訂查詢視窗
  • PAGE.RunModal() 開啟使用者頁面
  • 直接對 Rec 記錄物件寫入值

BasicTablePage.al 增加查詢視窗

C
page 60100 "Basic Table Page"
{
    PageType = List;
    ApplicationArea = All;
    SourceTable = "Basic Table";
    UsageCategory = Lists; // 讓它可以從搜尋直接找到

    layout
    {
        area(content)
        {
            repeater(Group)
            {
                field(ID; Rec."ID")
                {
                    ApplicationArea = All;
                    Editable = false;
                }

                field(Name; Rec."Name")
                {
                    ApplicationArea = All;
                    Lookup = true;

                    trigger OnLookup(var Text: Text): Boolean
                    var
                        UserRec: Record User;
                        ConfirmReplace: Boolean;
                    begin
                        if PAGE.RunModal(PAGE::Users, UserRec) = ACTION::LookupOK then begin
                            Rec."Name" := UserRec."User Name";

                            if Rec."Description" <> '' then begin
                                ConfirmReplace := Dialog.Confirm(
                                    '此筆資料已有描述 (%1),是否要覆寫為 "%2"?',
                                    false,
                                    Rec."Description",
                                    UserRec."Full Name"
                                );
                                if ConfirmReplace then
                                    Rec."Description" := UserRec."Full Name";
                            end else
                                Rec."Description" := UserRec."Full Name";
                        end else begin
                            // 使用者取消選取 → 清空欄位
                            Rec."Name" := '';
                            Rec."Description" := '';
                        end;

                        CurrPage.Update();
                        exit(true);
                    end;
                }

                field(Description; Rec."Description")
                {
                    ApplicationArea = All;
                }
            }
        }
    }
}

注意事項

  1. Users 是內建頁面(Page ID 9800)。
    PAGE::Users 是系統保留常數,可直接使用。
  2. 若出現「找不到 PAGE::Users」錯誤,請:確保 AL: Download Symbols 已執行;
  3. 若你想讓 Name 欄位也可手動輸入,只要不加 Editable = false; 即可。

執行的畫面如下:

image 4

修正 BasicTablePage.al 查詢視窗回傳值時,欄位:Name 沒更新

執行後,可以發現 Name 的值並沒有更新,而是空白,在 OnLookup 觸發器中,讓 Name 欄位在使用者選擇後,正確顯示所選使用者的名稱。根據上面的程式碼,在 OnLookup 觸發器中執行了以下動作:

  • 開啟了 User 列表頁面 (PAGE::Users) 供使用者選擇。
  • 如果使用者確認選擇,將 User 記錄的 User Name 欄位值賦予給當前記錄的 Name 欄位 (Rec."Name" := UserRec."User Name";)。

修正的程式碼

問題在於,當你在 OnLookup 觸發器中成功取得並賦值給 Rec."Name" 後,你需要確保這個新的值能夠被顯示出來。由於你已經設定了 exit(true); 並且呼叫了 CurrPage.Update();,照理說欄位會被更新。

然而,AL 語言中對於 OnLookup 觸發器的行為是:

  • 如果在 OnLookup 中返回 true,表示已經處理了查找 (lookup) 動作,並且 系統不會再執行預設的查找動作
  • 通常,當執行 exit(true); 時,需要手動將選取的值帶回給 Text 變數 (也就是欄位原本的值) 才能讓頁面欄位顯示新值。但程式碼已經直接修改了 Rec."Name",所以這部分邏輯是正確的。

已經使用了 CurrPage.Update() 來更新頁面,這通常是足夠的。

最可能的原因field(Name; Rec."Name")OnLookup 觸發器 雖然修改了 Rec."Name" 的值,但該值在離開觸發器後可能沒有被正確地寫入頁面控制項,特別是在列表頁上。

要確保選取的值能被正確地帶回並顯示在欄位中,需要在 OnLookup 觸發器的開頭使用傳入的 Text 參數 來設定回傳值,並在成功選擇後,將新的值賦予給這個 Text 變數

C
page 60100 "Basic Table Page"
{
    PageType = List;
    ApplicationArea = All;
    SourceTable = "Basic Table";
    UsageCategory = Lists; // 讓它可以從搜尋直接找到

    layout
    {
        area(content)
        {
            repeater(Group)
            {
                field(ID; Rec."ID")
                {
                    ApplicationArea = All;
                    Editable = false;
                }

                field(Name; Rec."Name")
                {
                    ApplicationArea = All;
                    Lookup = true;

                    trigger OnLookup(var Text: Text): Boolean // <--- 注意這裡的 Text 參數
                    var
                        UserRec: Record User;
                        ConfirmReplace: Boolean;
                    begin
                        // **修正步驟 1: 將選取邏輯獨立出來**
                        if PAGE.RunModal(PAGE::Users, UserRec) = ACTION::LookupOK then begin
                            // **修正步驟 2: 將選取到的使用者名稱賦值給 Rec 欄位**
                            Rec."Name" := UserRec."User Name"; // 確定這是你希望儲存的欄位

                            // **修正步驟 3: 將新的值賦予給 Text 參數** 
                            // 這是確保在 OnLookup 結束後,欄位顯示新值的關鍵步驟
                            Text := Rec."Name";

                            // 接著處理 Description 欄位更新邏輯
                            if Rec."Description" <> '' then begin
                                ConfirmReplace := Dialog.Confirm(
                                    '此筆資料已有描述 (%1),是否要覆寫為 "%2"?',
                                    false,
                                    Rec."Description",
                                    UserRec."Full Name"
                                );
                                if ConfirmReplace then
                                    Rec."Description" := UserRec."Full Name";
                            end else
                                Rec."Description" := UserRec."Full Name";

                            // **修正步驟 4: 移除 CurrPage.Update()**
                            // 通常情況下,當 OnLookup 傳回 true 且 Text 參數有值時,
                            // 系統會自動更新欄位。
                            // 在 List 頁面上,頻繁呼叫 CurrPage.Update() 
                            // 可能會導致效能問題或意外行為。

                            exit(true); // 傳回 true 表示你已經處理了查找動作
                        end else begin
                            // 使用者取消選取 → 清空欄位
                            Rec."Name" := '';
                            Rec."Description" := '';
                            Text := ''; // 同時清空 Text 參數
                            exit(true); // 傳回 true
                        end;

                        // 舊的 CurrPage.Update() 已經不需要了
                        // CurrPage.Update();
                        // exit(true); // 已經在 if/else 區塊中處理
                    end;
                }

                field(Description; Rec."Description")
                {
                    ApplicationArea = All;
                }
            }
        }
    }
}

修正說明

OnLookup 觸發器中加入了以下關鍵行:

  1. Text := Rec."Name";
    • OnLookup 觸發器的 Text 參數是一個 var (by reference) 變數,它代表了欄位的最終顯示值。
    • 在成功選擇使用者後,已經將值賦予給記錄 (Rec."Name"),但要讓頁面控制項立即顯示這個新值,你必須將這個值也賦予給 Text 變數。
  2. 移除 CurrPage.Update();
    • OnLookup 傳回 true 時,頁面控制項通常會自動使用 Text 變數的值進行更新。在列表頁上,手動呼叫 CurrPage.Update() 有時會干擾這個自動流程,或導致不必要的頁面重繪。透過正確設定 Text 參數並 exit(true),可以依賴 AL 的內建機制來處理。

這樣修改後,當使用者從 “Users” 頁面選擇一個使用者時,Rec."Name" 會被設定,同時 Text 變數也會被設定,確保名稱會立即顯示在頁面上的 Name 欄位中。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


內容索引