Northwind 2.0 你應該知道的事項

套用到
Microsoft 365 Access Access 2021 Access 2019

此處內容可能適用於 Northwind 2.0 開發者版與入門版。 

存取 VBA 參考

VBA (Visual Basic for Applications) 是所有 Office 產品中使用的程式語言。 學習 VBA 讓你能操作所有 Office 產品 (不只是 Access) 。
搜尋「how-to」時,務必尋找 Access 的具體範例,並將 Microsoft Access 納入搜尋中。 其他 Office 產品的解決方案通常會有效,但沒有保證。 Microsoft Access 是一款成熟的產品;這代表有很多例子;這對你來說很棒! 

這也代表你仍然可以參考較舊的 Access 程式設計書籍。 許多舊書仍可在二手書網站上以原價的一小部分購得。 請查看 Microsoft 官網,了解哪些版本的 Access 還在支援,然後選擇那些版本。

Office 支援資源結束 - 部署 Office |Microsoft Learn

以下是 Microsoft Access 文件的一些連結。

受信任位置與啟用內容

Microsoft Access 檔案就是 Office 檔案。 Office 檔案必須位於「受信任位置」或其內容已啟用。 這些物品被視為「安全」,是因為你製作了它們,或是來自可信來源。 每次開啟任何辦公室檔案時,都會進行「檢查可信地點」。 從現在起,我們將此稱為「可信/啟用」。 注意:若新版本應用程式發布並從非信任地點開啟,啟用內容的過程將重複。

了解更多關於可信地點的資訊:

巨集、功能與子版

巨集、函式和子是你將商業邏輯實作到 Access 資料庫的方式。 在開始之前,了解 範圍與可見度 非常重要。

事件 (如在表單 (按鈕、文字框、) 標籤等點擊控制項上的控制項) 觸發其他程序,例如新增、刪除記錄或開啟表單。 這些流程可以透過巨集或 VBA 實作。 Northwind 入門版主要使用巨集,部分 VBA 則是巨集無法執行所需功能。 Northwind 開發者版主要使用 VBA。 

有些控制類型內建了自動建立巨集的精靈。 例如,在表單中新增指令按鈕會開啟精靈,提供多種功能選項。 新增組合框會開啟一個精靈,可設定以尋找表單上的特定紀錄。 

導航面板是你查看和存取所有資料庫物件的主要方式,預設顯示在存取視窗的左側。 
Northwind 導航面板已經過客製化。 我們創建了一個自訂分類,稱為 Northwind Starter 2.0。 這讓我們能依功能區域組織物件。

VBA 中變數的範圍與可見性

了解 Access 或 Office 中的 範圍與可見性 非常重要。 你可以從這裡開始:

持續變數

有時候你需要一個變數在產生它的物件離開作用範圍後才存在。 詳見上方 的範圍與可見度 。 主要有三種方法:公開變數、TempVars 以及將數值儲存在本地資料表中。 許多開發者會混合使用這些工具。 每種方式都有優缺點。  關於每款的更多資訊請見此處:

VBA 模組公開變數:

臨時變異者:

將值儲存在局部表格中

  • 公開變數和 TempVar 是目前會話的,當應用程式關閉時會超出作用範圍。 但如果你想在不同工作階段保留使用者專屬變數呢? 你可以把這類數值儲存在本地資料表中。 在 Northwind 2.0 中,其中一個變數被儲存在一個稱為 SystemSettings 的表格中。 表格中的數值是 ShowWelcome。 這個數值會告訴 Access 你每次登入時是否想看到「歡迎」畫面。

OpenArgs 與 StringFormat ()

開發人員經常需要將參數從一個表單傳遞到另一個表單,或從表單傳遞到報表。 這些參數傳遞重要資訊,被調用函式將利用這些資訊來配置自身。 第二份表格或報告有幾種方式可以從第一份表格取得資訊。 以下是其中幾種方式:

  1. 第二種形式可以「回望」第一型態以獲取一些數值,可能是可見或隱形控制。  例如:
    lngCustomerID = Forms!FirstForm!cboCustomerID 
  2. 第一種形式可以將值儲存為全域變數或 TempVar。 例如:
    g_lngUserID = Me.cboUserID 
    TempVars.Add “UserID”, Me.cboUserID

在 Northwind 開發者版以及我們的專業生活中,常用的方法就是使用 DoCmd.OpenForm 或 OpenReport 的 OpenArgs 論證。 例如:

DoCmd.OpenForm "frmCompanyDetail", OpenArgs:=StringFormat("CompanyID={0} &CompanyTypeID={1}", Me.VendorID, ctVendor)

我們結合了兩種技術: (1) 使用 OpenArgs 傳遞 VendorID 和 VendorType, (2) 使用 StringFormat () 函式來建立例如這個字串:

CompanyID=5&CompanyTypeID=2 

這個字串看起來非常像瀏覽器中使用的查詢字串。 它包含一個或多個以&符號分隔的「名稱/值對」:

name1=value1&name2=value2

此類字串的優點是每個值都有名稱。 相比之下,更簡單的做法是將 OpenArgs 設為「5,2」。  在這種情況下,要找出每個數值代表什麼就需要花不少功夫。 命名每個值會讓查詢字串「自我描述」,這是很好的程式設計習慣。

DoCmd.OpenForm 的接收端,我們通常處於 Form_OpenForm_Load 事件中,想要將 OpenArgs 字串解析成其組件。

在 Northwind 裡,你可以用 StringToDictionary 函式來做到這點。 它會將類似 querystring 的函式解析成其組件。 這些元件接著會被儲存在 Scripting.Dictionary 物件中。 請注意,這需要你使用 Tools > References ,並將參考設定為 Scripting Runtime Microsoft (scrrun.dll) 。

字典物件的特點與優點包括:

  • 元素的順序並不重要
  • 簡單函式:新增與移除集合元素
  • 函式會循環檢視集合,讓你知道裡面有什麼
  • 一個 Exists 函式,讓你可以測試某個元素是否存在

字典物件的使用遍及《北風》。 例如,frmGenericDialog 中的 Form_Load 事件。

錯誤處理

在 Access 中用控制精靈建立的巨集很少包含錯誤處理;使用 Control Wizards 建立的 VBA 可能僅限於通用的 MsgBox 錯誤描述

在 Northwind 2.0 中,我們示範如何更好地使用 VBA 程式碼。 我們實作了所謂的 全域錯誤處理器。 任何程序中發生的錯誤都會呼叫全域層級的函式來顯示錯誤。 這裡最大的優點是錯誤處理是一致的。 如果訊息需要更改 (,例如要額外顯示錯誤號碼或將錯誤記錄到檔案) ,也必須只在一個地方完成。 

clsErrorHandler 是實作錯誤處理程式碼的類別模組。 類別模組將所有主函式與輔助函式整合於一個單元中,從而封裝程式碼。

AutoExec 巨集會呼叫 modStartup 中的啟動函式。 在 Starter Edition 中,函式會建立 一個 clsErrorHandler 的實例,並將其儲存為全域變數,供整個應用程式使用。 在開發版中,使用靜態類別——詳見類別模組頂端的註解。

事實上,程序中的錯誤處理程式碼非常一致,以至於我們能在不到五分鐘內,利用特定的 VBA 程式碼,為每個程序配備適當的錯誤處理程式,完成所有程式碼。 (程式碼未包含在範本) 中。 Northwind 2.0 入門版與開發者範本版最初皆配備此錯誤處理方法。 
'

改進錯誤處理

從 Northwind 開發者版 2.2 版本開始,錯誤處理程式經過改進,這要歸功於 Access 社群的回饋。 入門版則保持不變。 

基本上,2023 年 4 月) 2.0 版本 (錯誤處理程式為:

Public Sub HandleError(…)
    MsgBox Err.Description
End Sub

在 2.2 版本中,它升級為:

Public Sub HandleError (…, Optional ByVal IsEventProcedure As Boolean = False)
    If Not IsEventProcedure Then
        Err.Raise lngError, strErrSource
    End If
    MsgBox Err.Description
End Sub

為了理解這項變更的原因,我們先了解程式碼運行的原因:

  • AutoExec 巨集呼叫啟動程序,該程序在開啟第一個表單前先進行一些初始化。
  • 使用者與應用程式互動,例如開啟表單或點擊按鈕,觸發事件程序,如 Form_LoadcmdPrintInvoice_Click
    '

除了事件程序外,應用程式還有子程式和函式——大多在模組中——這些程式碼是從事件程序中呼叫的。 這些稱為「標準」程序。

在 Northwind 2.0 版本中,標準程序會自行處理錯誤,但不會以某種方式通知呼叫事件程序錯誤發生。 如果事件程序有後續程式碼,且該程式碼應不論程序先前處理的錯誤是否執行,這就可能很糟糕。 當然,我們可以用一個回傳成功或失敗的函式取代子程式,並相應地編碼事件程序,但這並非總是可行的選項。

在 Northwind 2.2 版本中,標準程序不處理錯誤訊息,而是使用 Err.Raise 將錯誤回報回呼叫事件程序。 呼叫事件程序會顯示該錯誤,並於 Exit_Handler 繼續。 這樣比較好,因為能讓呼叫程序順利結束。

要使用 Northwind 2.2 版本的程式碼,事件程序必須在 HandleError 中傳遞第三個參數,表示呼叫者是事件程序。 Northwind 開發者版已更新以實現此功能。

更強大的錯誤處理模組則支援陣列) 「堆疊」 (「推送與彈跳」程序。 第一個元素永遠是事件程序,因此不需要額外的參數。 此實作超出了 Northwind Dev Edition 的目標。

MRU 名單

MRU,或稱 最近使用的 清單,是最近使用的訂單和採購訂單清單。 你可能想經常回去查看這些,將它們放入下一個狀態。 MRU 清單在 Office 產品中常被視為最近使用的檔案清單,你可能想重新開啟這些檔案。

在 Northwind 開發版中,要實作 MRU 功能 (,而 Starter 版本中沒有,) 你必須先建立以下事項:

  1. 一張用來存放MRU資訊的表格。
  2. 當訂單或採購訂單 (PO) 開啟時,更新表格的程式碼。
  3. 程式碼可以更新 MRU 下拉選單。
  4. 當從色帶中選取 MRU 項目時,編碼載入該物品。

讓我們更詳細地看看這些。 

1. 用於儲存MRU資訊的表格。

MRU 表格的設計值得檢視,尤其是其索引。 請注意,有一個重複索引 SortIdx ,協助快速排序 MRU 項目,並有一個獨特的索引來強制執行每位使用者只能出現一次的商業規則。 例如,開同一張訂單兩次不會在 MRU 表格中產生兩筆紀錄。

此表格利用資料庫中所有與 MRU 相關的 PK (主鍵) 欄位皆為 AutoNumber,因此 PKValue 可使用長整數資料型別。

2. 在訂單或郵票開啟時更新表格的程式碼。

在 NW2 中,我們選擇只有在建立新紀錄時才加入 MRU 清單,而非在舊紀錄再次更新時加入。 我們當然可以將 AddToMRU 的呼叫從 Form_AfterInsert 轉到 Form_AfterUpdate ,以支持這個目標。

AddToMRUDeleteFromMRU 程序實作於 modGlobal,該模組為 Standard 模組,其公開程序可從任何形式看到。

AddToMRU (顧名思義) 將新項目加入 MRU 表格,然後若該紀錄已超過最大容量 (MAX_MRU_COUNT) ,則可選擇性地將其修剪回去,刪除最舊的紀錄。 最後一步可能是 Access 開發者中最不為人知的:功能區下拉選單必須刷新,這是透過呼叫 InvalidateControl 來完成的。 這是給色帶重跑初始化程序的訊號。 

3. 更新 MRU 下拉選單的程式碼。

在啟動時,以及呼叫 InvalidateControl 後,會執行一組複雜的函式來填充功能區。  這些程序由 uSysRibbons 表中的 Ribbon XML 呼叫,部分內容如下:


<group id="gCurrentStatus" label="MRU">
    <box id="bxMRU" boxStyle="vertical">
        <dropDown id="ddMRU"
                  getItemCount="ddMRU_GetItemCount"
                  getItemLabel="ddMRU_GetItemLabel"
                  getSelectedItemIndex="ddMRU_GetSelectedItemIndex"
                  getItemID="ddMRU_GetItemID"
                  onAction="ddMRU_OnAction"
                  screentip="Most Recently Used Objects">
        </dropDown>
    </box>
</group>

這四個回調函式會填充下拉選單。 請注意,這和 這裡 描述的標準組合盒概念非常相同。

如果你取消 modRibbonCallback 中的 Debug.Print 行並重新啟動應用程式,立即視窗會顯示如下序列:


ddMRU_GetItemCount    ddMRU    6 
ddMRU_GetItemLabel    ddMRU    0      Order 60, Proseware, Inc.
ddMRU_GetItemID       ddMRU    0       2 
ddMRU_GetItemLabel    ddMRU    1      Order 62, Best For You Organics Company
ddMRU_GetItemID       ddMRU    1       4 
ddMRU_GetItemLabel    ddMRU    2      Order 63, Wide World Importers
ddMRU_GetItemID       ddMRU    2       5 
ddMRU_GetItemLabel    ddMRU    3      Order 66, Proseware, Inc.
ddMRU_GetItemID       ddMRU    3       8 
ddMRU_GetItemLabel    ddMRU    4      Order 67, Best For You Organics Company
ddMRU_GetItemID       ddMRU    4       9 
ddMRU_GetItemLabel    ddMRU    5      Order 68, Adatum Corporation
ddMRU_GetItemID       ddMRU    5       10 
ddMRU_GetSelectedItemIndex  ddMRU    0

我們可以看到 Access 首先呼叫一個程序,回傳 ByRef 參數中要載入的項目數量,ddMRU_GetItemCount。 這也是我們打開 MRU 表格查詢並快取的時候,因為它即將被多次使用。 

功能區接著會反覆呼叫兩個程序,取得兩欄下拉選單的 ID 與 Label 值。 

最後,它會呼叫一個程序來決定應該選擇哪個項目。 (就我們而言,是第一個。)

4. 當從色帶中選取MRU物品時,載入物品的代碼。

與其他 Ribbon 項目一樣,Ribbon XML 中的 OnAction 屬性指定了一個回調函式來執行該動作:


onAction="ddMRU_OnAction"

此程序實作於 modRibbonCallback 中。 它會重複使用已開啟的紀錄集來尋找包含所選項目的紀錄,然後根據所需的 TableName 開啟對應表單,並傳入要載入的 PK 值。

深入了解