簡介
在進行絕大多數應用程式設計時,首先要考慮的設計問題之一是如何?圖形化使用者介面 (GUI) 導航,即如何向使用者顯示新的資訊螢幕。在傳統的 PC 環境中,應用程式只為每個資訊螢幕建立一個新表單(或對話方塊)。
在開發人員使用 Microsoft? eMbedded Visual Basic? (eVB) 之前,這種方法一直很有效。使用 Microsoft? eMbedded Visual Basic? (eVB) 後,如果應用程式中包含很多表單,則很快會將寶貴的記憶體資源用盡;如果在所有表單中都需要使用菜單,則最終不得不複製大段代碼以保持行為的一致性。
為解決這一問題,許多 eVB 開發人員選擇在單個表單中使用多個架構。開發人員要使用 Microsoft? .NET Framework 精簡版,應再次檢查產生有效使用者介面 (UI) 引擎(未與使用此引擎的應用程式緊密耦合)的方法。
限制資源的使用
使用現今的電腦,傳統型應用程式開發人員幾乎無須顧慮記憶體的使用方式。而智慧型裝置開發人員則不得不考慮這個問題。要盡量減少記憶體的使用,方法之一是儘可能重複使用類,尤其是 Forms 類。
例如,讓我們來看看可以在當地錄影帶出租商店找到的電影查詢應用程式。我們假設此應用程式啟動時將顯示所有電影的列表。不可否認,這將是一個龐大的列表,而且是一個很糟糕的應用程式設計,但出於示範目的我們假設它是合理的。
現在,如果某位使用者選擇了一部電影的名稱,應用程式將顯示另一個表單,其中顯示這部電影的詳細資料(包括演員)和另一個類似的推薦電影列表(“如果您喜歡這部電影,那麼可能也會喜歡 . . . ”)。
圖 1:螢幕流程圖
在流程圖中,我們假設使用者既可以從新列表中選擇電影,僅將有關這部電影的新資訊重新填充到當前表單中;也可以選擇演員。如果選擇演員,可以立即顯示演員的資訊,包括合演的明星以及另一個電影列表,這次將顯示列出此演員出演的電影列表。
很顯然,如果查看的每個表單都是帶有所有關聯資料的新表單執行個體,則應用程式將很快佔用大量記憶體。而且,Windows CE 裝置的處理器效能通常都趕不上台式電腦的效能,使用新資料載入每個表單將產生一個緩慢、響應遲鈍且使用者不友好的應用程式。
堆棧和緩衝
現在,我們將深入瞭解另一個對使用者有益的功能,即查看上一個表單,它與瀏覽器中的[上一頁] 按鈕功能類似。這就意味著,應用程式必須“記住”使用者的位置。
在某些應用程式中實現此操作比較簡單,因為表單的“目標”和“起始”路徑很有限,可通過寫入程式碼來實現。對於我們的應用程式,您可以看到這些表單顯示給使用者的順序幾乎是隨機的(參見圖 1)。
因此,我們現在需要瞭解基本要求。首先,我們希望盡量減少記憶體中保留的表單數;其次,我們希望記錄使用者查看過的表單以及查看順序。事實上,我們可能還希望跟蹤在每個表單中查看的資料,但是完成架構後,它只是一個簡單的新增部分,請讀者自己練習。
那麼,到底什麼解決方案能夠滿足這些要求呢?要盡量減少表單數,可以使用所謂的緩衝。緩衝只是一種儲存機制,可用於儲存已載入表單的單個副本,並在可用時重複使用緩衝的表單。這就意味著當使用者要查看表單時,應首先檢查它是否位於緩衝中。如果不在緩衝中,則按正常方式載入。但如果不是簡單地顯示緩衝的表單,而是要在記憶體中儲存一個新副本,則還需要考慮實際建立表單的時間。 可以使用堆疊追蹤已檢視的表單的記錄。假設查看的每個螢幕都可以表示為一張卡片,而這些卡片可以插入到堆棧中。使用者查看每個螢幕時,可以將卡片插入(壓入)堆棧中。也就是說,位於頂部的卡片是最後查看的表單;要查看記錄,只需從堆棧中刪除(或彈出)卡片即可。使用這種方法,應用程式可以“撤消”由使用者執行的任意數量、任何順序的瀏覽操作。
設計 FormStack
我們已經瞭解了需要滿足的要求和基本方法,現在讓我們看看如何?它們。首先,讓我們仔細考慮完成這項工作需要執行的操作。 我們將需要 FormStack,它可以作為類來實現。它需要一種壓入和彈出表單的機制。還需要一個用於存放實際表單的緩衝和一個堆棧,鑒於前面介紹的資源問題,緩衝不應該儲存表單的實際副本,而應該只儲存一個標識符。我們將表單類的類型用做字串。
載入表單並非瞬間即可完成,尤其是當表單包含許多控制項時。必須建立表單的所有控制項,通常可能需要為列表和下拉式清單提取資料。提取資料後,則需要填充控制項。如果在應用程式進程的主線程中執行以上所有操作,則在載入表單的過程中,使用者必須等待很長時間。 為了減少等待時間,需要添加兩個功能。首先,應該使用輔助線程提取所有需要的資料;然後,添加一個方法,允許應用程式在緩衝中“積極式載入”一個表單。這就使應用程式能夠在後台或在空閑時(例如啟動畫面過程中)載入“重型”表單。
實現緩衝和堆棧時還需要考慮其他一些問題,但是我們先定義所需的核心內容。由 FormStack 類完成的實際操作幾乎都會在“壓入”或“積極式載入”過程中啟動,因此,我們先看一下其中的邏輯。
圖 2:執行壓入
圖 2 顯示了“壓入”的執行流程圖。正如您看到的,我們只檢查緩衝是否存在,如果不存在,則調用“積極式載入”;否則,只顯示表單並將其添加到堆棧中。
圖 3:執行積極式載入
儘管積極式載入(參見圖 3)執行的步驟略多一些,但還是比較直觀。首先,必須建立 Form 類。在調用標準 InitializeComponent 方法建立表單的所有控制項之前,啟動資料查詢線程。
一個重要的步驟是,建立所有控制項後,必須等待資料查詢線程完成才能填充控制項,因此需要處理一些基本的線程同步。
填充控制項後,最後一步是將控制項添加到緩衝中。
StackForm 類
FormStack 的實際代碼實現與這些流程圖基本相同。首先,我建立了所有表單必須繼承的 StackForm 基類。這樣,即使表單不使用基本架構,也可以確保其存在(例如資料線程和控制項填充方法)。 StackForm 執行以下任務:
添加一個 EventHandler 來截取表單中的 Closing 事件,這樣當表單關閉而不是實際卸載時可以調用“彈出”。
提供 LoadData 輔助線程函數和 Populate 函數,並在輔助線程完成後調用它們。
為 InitializeComponent 提供一個公用的抽象方法簽名。這很重要,因為 Form Designer(表單設計器)預設情況下建立 InitializeComponent Private,而 FormStack 類又需要調用它。通過提供一個抽象實現,編譯器會確認我們已進行了此更改。
遺憾的是,Visual Studio .NET 2003 中的 Form Designer(表單設計器)只能使用從 Form 類繼承的內容。我執行的操作是:當表單繼承 Form 時使用設計器設計的表單,然後在產生期間將基類更改為 StackForm。儘管這有點不方便,但要比手動對表單進行編碼好一些。
FormStack 類
然後,我建立了 FormStack 類。FormStack 實現“壓入”和“積極式載入”的方法與流程圖相同。堆棧本身是使用 ArrayList 實現的,因此便於添加或刪除項目。表單 Cache 是通過從 CollectionBase 繼承 FormStack 類來實現的。它提供了一個 List 成員集合,大大簡化了 FormStack 對象的預存程序。
除了“壓入”和“積極式載入”,我還實現了“彈出”(只需刪除堆棧中的彈出項目並向下顯示下一個 StackForm)。它並未從緩衝中刪除 StackForm,我也未實現完成該操作的函數。儘管在某些情況下,從緩衝中實際釋放表單的方法可能更有用,但它過於複雜,已超出本文的範圍。
實際上該體繫結構較大的障礙之一是運行應用程式。.NET Framework 精簡版提供了進入點 Application.Run,它載入一個表單並提供一個訊息泵。使用 Run 在體繫結構中無法正常運行,因為它需要一個執行個體化的表單,應用程式中的每個其他表單必須在該初始表單中運行。我們需要提供自己的訊息泵,以調度系統訊息,並為應用程式實際結束提供一個簡單方法。 雖然這看似複雜,但實際上卻非常簡單。我使用一個緊密的迴圈在 FormStack.Run 中實現了訊息泵,只要緩衝中包含表單,該迴圈即調用 Application.DoEvents。這就意味著在調用 Run 之前必須先將 StackForm 壓入堆棧。我還實現了只清除緩衝的互補方法 Stop,該方法能夠使訊息泵退出,並運行到完成。
最後,我重寫了 StackForm.ToString。這隻是提供了一個極好的字串,允許您瞭解緩衝中 StackForms 的數量和堆棧的精確內容。
繼承 StackForm 類
最後需要考慮的是如何真正實現 StackForm。由於 StackForm 是一個抽象類別,因此無法將其執行個體化。而必須在自行派生的類中繼承 StackForm。繼承性是物件導向編程的優點之一,可以變得非常複雜,因此我假設您已經完全理解它,只介紹從 StackForm 中繼承時需要牢記的特殊事項。
首先,建構函式必須調用 StackForm 基本建構函式。這是因為基本建構函式圍繞 Closing 事件。
接下來,預設建構函式調用 InitializeComponent。在我們的實現中,FormStack 調用 InitializeComponent,因此我們不希望建構函式真正調用它,但是由於我們確實希望設計者能夠呈現表單,因此需要在此處進行調用。.NET Framework 精簡版的一大優點是具有一個編譯器指令,允許我們按照以下方法排除調用:
#if NETCFDESIGNTIME
InitializeComponent();
#endif
最後,我們還可以選擇實現 DataThread 和 Populate 方法(如果需要)。
同樣,DataThread 在單獨的線程中執行,並應該用於提取資料。請記住,在 DataThread 完成其工作之前,StackForm 不會被完全壓入和顯示,因此不要執行可能中斷執行的任務(例如調用 Sleep)。如果選擇實現 DataThread,則需要做的最後一件事(並且必須實現以發出線程完成訊號)是調用 base.DataThread。
最後,InitializeComponent 和 DataThread 方法完成後,將調用 Populate,以便在 StackForm 可見之前有機會填充列表、下拉式清單或其他控制項。
請記住,由於這些 StackForms 已被緩衝,因此 DataThread 和 Populate 只在 StackForm 載入到緩衝時被調用,而不是每次顯示時都調用。
小結
正如我在本文開頭提到的,在開發較為複雜的應用程式時,您將發現 FormStack 缺少一些功能,例如根據顯示內容重新整理資料的功能。請記住,堆棧變數本身不僅易於儲存表單類型,而且可以儲存資料庫中的實體 ID 值並允許快速重寫表單。
編程的優點是隨時可以添加其他功能,但應始終記住智慧型裝置編程與傳統型應用程式編程有著本質上的區別。裝置資源通常都很有限,處理器的速度也往往慢很多,因此,作為應用程式編程人員,要想開發成功的應用程式,必須記住以上兩點。FormStack 這個工具可以協助開發人員盡量減少對應用程式資源的影響,同時可以管理 GUI 引擎的複雜功能,這樣可以通過緩衝快速更改表單並通過堆棧輕鬆導航。