通過 .NET Compact Framework 最佳化 Pocket PC 開發

來源:互聯網
上載者:User
發布日期: 1/7/2005 | 更新日期: 1/7/2005

Dave EdsonJohn Socha-Leialoha

本文討論:

能夠使在 .NET Compact Framework 環境中編程變得更容易的提示

能夠使基於 Pocket PC 的應用程式更快啟動並執行技巧

重要的 .NET Compact Framework 類

.NET Compact Framework 視窗問題

代碼下載可從以下位置獲得:NETCompactFramework.exe (270KB)

如今,程式員有很多工作都無需去做了。藉助於 .NET,最後終於有了一個使應用程式開發真正容易的架構。開發人員幾乎不用花費精力來管理記憶體,以至於我們很容易濫用它。所有優雅的控制項、表單、視窗系統以及 GDI+ 功能都使應用程式開發變得如此容易。是的,很容易 — 直到您必須為 Pocket PC 這個帶有慢得令人痛苦的圖形(讀取:沒有加速)、沒有硬碟並且只有 32MB RAM(您實際上只能使用其中的大約 2 或 3 MB)的 200MHz 平台編寫程式為止。除了硬體限制以外,當您一頭紮入 .NET Compact Framework,卻發現自己最喜歡的某些功能並未實現時,還會產生一種沮喪的感覺。突然之間,您所具有的編寫巧妙的、令人歎為觀止的 C# 應用程式的夢想變成了一個噩夢。

請不要絕望。.NET Compact Framework 確實可以用來編寫極好的代碼和應用程式。只要您考慮到幾個方面並且願意遵循一兩個規則,您就還能夠改善效能。我們已經使用用於 Pocket PC 的 C# 編寫了很多應用程式,從而得到了既小又快的、完美的、靈巧的應用程式。

本文劃分為兩個主要部分:技巧和最佳化。在技巧部分,我們將回顧一些使我們作為 .NET Compact Framework 程式員的工作變得更容易的優雅技巧。在最佳化部分,我們將討論能夠提高效能、減少載入時間和減小記憶體足跡的技術。本文將提供範例程式碼,以協助您使應用程式變得既小又快。

基本技巧和我們的 Test Bench

當您啟動 Visual Studio .NET 以產生我們的初始應用程式時,您將需要確保在主表單的建構函式中(恰好在對 InitializeComponent 的調用之後)添加下列程式碼:

#if DEBUG    MinimizeBox = false;#else    MinimizeBox = true;#endif

這使您可以實際關閉該應用程式,而不是僅僅將其最小化(在調試器外部進行測試時,這可能很有用)。下一個重要任務是發掘您要為其編程的裝置的全部能力。

.NET Compact Framework 恰如其名 — 它確實是壓縮版。令人欣慰的是,它包含了 Interop 服務,因此您可以調用裝置上的任何 Win32CE API。由於這些 API 中有很多都需要視窗控制代碼,因此您需要獲得一個視窗控制代碼。在 .NET Compact Framework 的版本 2.0(它重新啟用了 Handle 屬性)發布之前,請使用下列程式碼來獲得 hWnd:

control.Capture = true;IntPtr hwnd = Win32.GetCapture();control.Capture = false;

有關 Win32 類的資訊,請參閱提要欄“Windows CE 命名空間”。

既然 Win32 API 又重新為您所掌握,那麼您還可以採用另外一種方式來方便地使用 hWnd。您可以通過用 eMbedded Visual C++ 4.0 編寫的 Win32 DLL 來將您自己的視窗子類化,並且使用非常手段。但是,請記住一件事情:Interop 服務不會使 DLL 一直載入在記憶體中。在 P/Invoke 的內部有一個 LoadLibrary/FreeLibrary 對。如果您希望您的 DLL 子類化您的應用程式(或系統中的任何應用程式),則 DLL 需要一直載入在記憶體中,直到您準備好讓它離開為止。該問題有一個很好的解決方案:只須在開始時調用該 DLL 中的 LoadLibrary,然後在完成工作時調用該 DLL 中的 FreeLibrary。引用計數將突然提高一個等級,並且 P/Invoke 的 LoadLibrary/FreeLibrary 對將變得很溫和。

圖 1 MSDN Test Bench

本文將討論很多個不同的主題。我們已經編寫了一個 Test Bench 程式(如圖 1 所示),它結合使用了本文討論的很多概念。它合并了提要欄“Windows CE 命名空間”中介紹的 Win32 類,以及本文後面討論的 BaseControl 類。

笨拙的、令人討厭的對話方塊

那些不知道 .NET Compact Framework 中的對話方塊行為的使用者和開發人員在開啟強制回應對話方塊時,可能會變得不知所措。.NET Compact Framework 中的對話方塊並不是通常意義上的對話方塊。相反,它們只是一些出現在現有表單之上的新表單,而且在您關閉表單之前,它們不會將控制返回到調用方。如果您使用如下所示的代碼顯示對話方塊

  BadDialog test = new BadDialog();   test.ShowDialog();

則對話方塊將作為頂級表單出現。當使用者從您的程式中切換出去時,有趣的事情就開始了。最後,他們希望返回到您的應用程式,並且他們轉到“System | Settings | Memory | Running Programs”頁,在這裡他們看到您的主應用程式和對話方塊都被列出。如果使用者單擊頂級應用程式而不是對話方塊,則主應用程式視窗將顯示,但是該視窗將是不活動的,因為它正在等待對話方塊關閉。

要看到這一情況的發生,請單擊 Test Bench 應用程式中的“Bad Dialog”按鈕。現在,請從“Settings”螢幕轉到正在啟動並執行程式的列表,並且啟用 Test Bench 應用程式(請參見圖 2)。如果您單擊任何按鈕,則它們只是發出聲響,因為該視窗被禁用。您甚至無法關閉它。因此,使用者只能採用他們所知道的方式進行響應:一通單擊(實際上是在行動裝置上一通狂敲)。擺脫這一困境的方式是,返回到正在啟動並執行程式的列表並啟用“Bad Dialog”項。更糟糕的是,一旦您關閉該對話方塊,就可能彈出另外一個應用程式,因為當您在不同的程式之間切換時,視窗的 Z 順序可能已經更改。

圖 2 使人迷惑的資訊

為瞭解決這些問題,以及其他幾個與存在多個表單相關的問題,我們建立了 Dialogs 類 — 該類帶有一個名為 ShowDialog 的靜態成員。以下是該類完成的工作:

通過將“Running Programs List”中的父表單的標題設定為空白字串,將該表單隱藏。

將對話方塊的標題設定為父表單所使用的相同名稱,從而保留您在“Running Program List”中看到的名稱,無論哪個對話方塊位於最頂層。

捕獲新對話方塊的 Activated 和 Closed 事件,以便可以跟蹤哪個表單是“最頂層”的表單。

為該對話方塊調用 ShowDialog。

每當任一“父”表單被啟用時,啟用“最頂層”表單並將其提升至頂層。

這整個過程有時可導致另外一個應用程式在您關閉強制回應對話方塊時瞬間可見,但是除了這一點以外,一切都按照使用者期望的那樣工作。通過單擊 Test Bench 應用程式中的“Dialog Fix”按鈕,您可以看到 ShowDialog 的這一增強版本是如何工作的。請注意,對話方塊上的標題與主表單相同,並且只有一個表單出現在“Running Programs List”中。

更好地控制操作功能表

在典型的基於 Pocket PC 的應用程式上單擊並保持不動,您將獲得那些跳動的點,後面跟著一個操作功能表。對於使用者而言,這非常好。對於程式員而言,這是令人煩惱的,因為沒有收到 MouseDown 事件。這意味著沒有機會在顯示操作功能表之前執行一些操作,例如,首先禁用該操作功能表或者更改某個選項。要想繞過這一行為,需要對運行庫耍幾個花招。通常,只須在使用者單擊並保持不動時,設定控制項的 ContextMenu 屬性以便將其與操作功能表相關聯。在我們的解決方案中,我們沒有設定控制項的 ContextMenu 屬性;相反,我們模仿 Control 類中內建的代碼來處理 MouseDown 事件並顯示菜單。為了使這一點對程式員儘可能地透明,我們開發了一個新的控制項基類,並將其聰明地命名為 BaseControl。它封裝了我們希望我們的自訂控制項具有的很多行為。有關這些功能的簡短列表,請參見圖 3。消除預設行為並將其替換為我們自己的行為的關鍵是重寫 ContextMenu 屬性。這樣,Control.ContextMenu 屬性仍然為空白,而 BaseControl.ContextMenu 則包含我們需要的資訊。

圖 4 顯示了 BaseControl 的完成這一工作的四個方法:OnMouseDown、CheckContext、ShowContext 和 RecognizeGesture。如果您需要更改選項或者執行其他某種操作,則您自己的自訂控制項可以重寫 OnMouseDown 方法。CheckContext 方法調用 OnBeforeContextPopup,後者接著激發 BeforeContextPopup 事件。然後,事件處理常式就有機會更改或取消該操作功能表,或者完成其他特定於應用程式的工作。RecognizeGesture 方法完成使跳動的點出現的實際工作。ShowContext 顯示該操作功能表。

圖 5 操作功能表技巧

在 Test Bench 程式上,單擊“Context Menu”按鈕,您將看到圖 5 中顯示的螢幕。頂部的灰框控制項是一個正常建立的控制項。單擊它和它下面的列表框可以顯示相關事件。正如前面說明的那樣,如果您單擊並保持不動,則不會發生滑鼠按下事件。該螢幕的下半部分使用了一個控制項,該控制項利用了我們的技巧。該控制項的行為就像正常的控制項一樣,不同之處在於,您在使用者觸控螢幕幕後立即得到那個重要的滑鼠按下事件。

可設計的自訂控制項

為 .NET Compact Framework 設計自訂控制項不像為案頭設計自訂控制項那樣容易。如果能夠使用設計器建立一個自訂控制項,然後在設計應用程式時使用該新控制項,那會很不錯。在案頭上,這一切完全是無縫地工作,因為案頭 System.Windows.Form.Control 對象背後的代碼內建了所有設計器支援代碼。Microsoft.WindowsCE.Forms.Control 不具有該代碼,因為該代碼會消耗記憶體。

但是,您可以使用一個小技巧使這一情況發生。在設計新的自訂控制項時,從表單而不是控制項開始。現在,您可以使用所有設計器工具來產生自訂控制項。當然,需要為此付出代價,但是不太高:在對應用程式(不是控制項)使用設計器時,您無法向設計器的工具箱中添加自訂控制項,因為您所建立的是表單,而不是控制項。因此,您將需要使用如下所示的代碼建立“控制項”的執行個體:

MyControl = new MyCustomControl();Controls.Add(MyControl);   MyControl.Location = new Point(x, y);   MyControl.Size = new Size(dx, dy);   MyControl.Visible = true;

請注意,我們同時設定了位置和大小。當您使用表單作為控制項時,.NET Compact Framework 將自動根據使用全屏的需要設定它的大小。要繞過這一行為,您必須自己設定大小。

但是,有一個小問題。在某些情況下,添加實際上是表單的自訂控制項將導致螢幕頂部的標題中出現額外的閃爍,因為它首先更改為顯示新表單中的標題,然後又更改回來。繞過該行為需要撤消我們的技巧。我們需要將基類重新更改為控制項!要讓該控制項可以根據它是希望成為表單還是控制項進行翻轉,您可以添加一個條件編譯常量,並且為您基於該表單技巧建立的每個自訂控制項修改類定義。

圖 6 DESIGN 條件常量

圖 6 顯示了名為 DESIGN 的條件常量,它只在編譯的調試版本中設定。修改自訂控制項(表單)的類定義,以使其看起來如下所示:

   // DESIGN is only set in Debug builds   #if DESIGN       public class MyCustomControl :           System.Windows.Forms.Form   #else       public class MyCustomControl :           System.Windows.Forms.Control   #endif

圖 7 自訂控制項

如果 DESIGN 常量未定義(對於發布模式不應當如此),則自訂控制項將作為控制項而不是表單編譯。對於偵錯模式,您可以使 DESIGN 常量保持定義,並且在該模式下進行測試。但是,如果您願意的話,可以總是移除它以便測試代碼,然後重新添加它以便可以重新設計表單。如果您先是將其移除,然後又添加了它,則您可能需要重新編譯應用程式,然後 Visual Studio 才能重新意識到它可以設計該表單。

在 Test Bench 應用程式中,單擊“Custom Control”按鈕將呈現一個帶有該自訂控制項的對話方塊(請參見圖 7)。如果您轉到 Test Bench 的原始碼,則您將看到 Tests 檔案夾中的 MyCustomControl 類,您能夠從設計器中編輯該類(請參見圖 8)。

圖 8 MyCustomControl

最佳化

編寫既小又快的代碼總是一個好主意,但是在為 .NET Compact Framework 編寫代碼時,這一點卻是關鍵性的。您確實沒有很多的記憶體和很高的速度可用。在設計時,您需要考慮將要執行什麼代碼,以及將如何使用您的資料結構。由於記憶體非常寶貴,因此避免製作資料副本很重要。而最為重要的是,代碼只應在需要資料時才建立它,並且在完成工作時丟棄它。

另一個需要考慮的問題是,何時使用 .NET 代碼以及何時使用 Win32 代碼。有時,使用 Win32 代碼要快速和容易得多(的確如此!)。您將很高興地知道,大多數時候,使用 .NET 代碼要更容易。儘管如此,請注意,我們不是鼓勵您“回到遠古時代”,只是為了使應用程式提高一點兒效能。

簡言之,.NET Compact Framework 對於記憶體管理、事件處理、物件導向的編程、使用者輸入、國際化和檔案 I/O 很擅長。.NET Compact Framework 面臨最大挑戰的領域是圖形、視窗管理和資源管理。通過放棄 Graphics 對象並返回到 Win32 GDI,可以獲得速度和功能方面的巨大改善。對於視窗管理,可以調整 .NET Compact Framework 代碼以顯著改善效能。但是建立視窗需要花費很長時間。如果您具有一個索引標籤控制項,並且該控制項帶有跨很多個頁的很多個控制項,則該索引標籤控制項的初始化可能需要相當長的時間。這是因為每個頁上的每個控制項都是在載入時建立的。稍後,我們將介紹索引標籤控制項類的替代物,它主要是帶有少量 Win32 調用的 .NET 代碼,該代碼會產生一個能夠飛快載入並動態載入或放棄不可見頁的索引標籤控制項。

衡量應用程式效能的一種方式是使用 Environment.TickCount 屬性,如下所示:

int t = Environment.TickCount;   // <do your stuff here>   t = Environment.TickCount - t;

當然,沒有什麼事情會如此容易。由於 .NET 可執行檔實際上是由 Microsoft 中繼語言 (MSIL) 程式碼群組成的,因此涉及到即時 (JIT) 編譯器。第一次調用您的函數時,所花費的時間可能比隨後的調用多。在運行測試時,請記住這一點。還要注意,模擬器和實際裝置可能就完成任務所需要的時間量給出極為不同的結果。因此,您將需要在實際裝置上運行所有基準測試,並且不能通過調試器。

我們的第一個樣本需要回溯到 Microsoft Systems Journal 1995 年 5 月刊中的一篇題為“Grab Bag of Gotchas and Goodies for Programming in Windows”的文章。在經過這麼多年以後,ExtTextOut 仍然是在螢幕上顯示文本的最快速的方式。它還可以用透明的背景進行繪製,而 .NET Compact Framework 中不支援所繪製的標籤(只須在 PictureBox 上方放置一個標籤,您就可以瞭解這一點)。

圖 9 GDI 速度測試

在 Test Bench 程式上,運行 GDI 速度測試。當您第一次運行該測試時,JIT 編譯器將汙染結果。在我們的裝置上,常規的、使用 DrawString 的 .NET 方法需要 51 毫秒,而使用 GDI 方法 ExtTextOut 只需要 22 毫秒。如果您花費時間再次運行該測試,您將看到速度明顯提高,如圖 9 所示。

讓資源變得豐富

當需要嚮應用程式中添加映像和文本時,使用資源而不是對文本進行手動編碼或者使用外部影像檔是一個好主意。Visual Studio .NET 2003 具有建立字串資源所需的所有工具,但是它缺少內建的對管理映像資源的支援。因此,您需要求助於 Visual Studio .NET 隨附的 ResEditor 應用程式範例,或者求助於第三方應用程式。我們最喜歡的是一個名為 Resourcer for .NET 的免費程式,您可以從 www.aisto.com/roeder/dotnet 獲得它。它的用法很簡單,並且附有原始碼,因此,如果您願意,可以對其變更。

當您單擊 Resources 按鈕時,Test Bench 程式會將一個位元影像資源載入到表單中。Test Bench 包含一個名為 MyResources 的類,該類使得這些資源的使用變得容易得多。以下是您為載入映像而需要完成的所有工作:

m_image = MyResources.Icons.LoadBitmap("resTest");

僅僅通過使用“Add New Item”對話方塊添加新的程式集資源檔,就可以建立字串資源檔。為了將資源群組合到一個位置,我們在名為 Resources 的檔案夾中建立了一個名為 strings.resx 的檔案。雙擊 strings.resx 檔案,您將看到一個螢幕,如圖 10 所示。要載入字串,只須完成以下工作:

string s = MyResources.Strings["id"];

正如我們在前面提到的那樣,使用映像資源要求使用外部資源編輯器。如果您使用的是 Resourcer,則可以從“Add”菜單中選擇“Add File”,輸入要添加的映像資源的名稱,然後選擇檔案。如果您運行應用程式範例並單擊“Resource Test”按鈕,則您將看到從資源檔中同時載入了一個字串和一個映像。

圖 10 添加字串資源檔

如果您要編寫商務應用程式,則有很多理由將資源放到單獨的 DLL 中。最重要的理由是您可以提供各種皮膚。如果您可以為使用者提供多種應用程式外觀,則可以增加潛在的客戶群。我們將不會詳細討論如何編寫可換膚的應用程式,但是我們將假設字串、位元影像和位置資訊都需要放到某個 DLL 中,並且該 DLL 可以容易地與另一個包含不同皮膚的 DLL 進行交換。

用 .NET Compact Framework 動態載入 .NET DLL 並且從中獲得資源並不是一件令人愉快的事情。即使是的話,您也會在您的資源被載入之前很快睡過去。對於 .NET 資源,還有一個惱人的行為:如果您從 .NET 資源中載入映像,然後調用 Dispose,則該映像會永久消失。如果您嘗試從 .NET 資源中重新載入它,則資源載入程式將向您返回一個有效、但不會繪製自身的 Image 對象。使該映像重新緩衝自身的唯一方式是,建立資源載入程式的新執行個體(這要花費時間)。因此,讓我們完全避開這些資源,並且從頭開始。

在我們討論載入位元影像以前,我們需要討論如何呈現它們。在涉及到速度時,只要您使用的是裝置相關位元影像 (DDB),Win32 BitBlt 函數就仍然能夠滿足需要。如果您嘗試 Blt 到裝置無關位元影像 (DIB),則會在速度方面遭受巨大打擊。

在 Pocket PC 裝置的系統目錄內部,有一個可以解壓縮下列所有種類的映像格式的 DLL:JPG、PNG 和 GIF(這裡只舉數例)。我們在我們編寫的一個 C++ DLL 中利用了該功能,以便可以從 DLL 中載入映像資源,並且將該資訊作為 DDB 返回給我們。該 DLL 具有兩個公開的函數:

HBITMAP LoadBitmapFromFilename (HDC hdc, LPCTSTR szFileName);HBITMAP LoadBitmapFromResource (    HDC hdc, DWORD dwResourceID, LPCTSTR pcszClass, HMODULE hModule);

這兩個函數的第一個參數是一個裝置上下文控制代碼 (HDC)。我們將使用我們的具有相應名稱的 Win32 類獲得當前表單的 HDC,並將其傳遞到這些函數中。然後,這些函數使用傳入的 HDC 來判斷如何將 DDB 返回到您的應用程式中。第一個函數非常容易。第二個函數可以用一些程式碼片段說明。以下是 C++ 資源 DLL 的 RC 檔案中的代碼:

1001    PNG    DISCARDABLE    "msdn.png"

以下則是能夠將該資源載入到應用程式中的代碼:

[DllImport("ImageLoader.dll", CharSet=CharSet.Unicode)]public static extern IntPtr LoadBitmapFromResource(    IntPtr hdc, uint dwResourceID, string pcszClass, IntPtr hModule);...hInstance = Win32.LoadLibary ( stringDLLName );if (hInstance != IntPtr.Zero){    IntPtr hdc = Win32.GetDC ( this.Handle );    hbmp = LoadBitmapFromResource ( hdc, 1001,             "PNG", hInstance );    Win32.ReleaseDC ( hwnd, hdc );    Win32.FreeLibrary ( stringDLLName );}

您會發現,該代碼非常快速地載入位元影像。並且,由於 JPG、PNG 和 GIF 格式受到支援,因此資源 DLL 可以非常小。除了映像以外,您還可以使用 Win32.LoadString 方法來填充特定於語言的資訊,甚至是字串形式的位置資訊。

圖 11 資源測試

在我們的 Test Bench 應用程式中,單擊“Resource Test”按鈕以查看比較(請參見圖 11)。在“Resource Test”螢幕中,單擊“Load”按鈕從資源 DLL 中載入位元影像(使用您剛剛看到的代碼,並使用標準的 .NET 資源調用)。每一部分代碼都會載入資源,繪製它,然後丟棄它。當您第一次運行測試時,您將會看到在速度方面存在令人吃驚的差異。隨後的運行不像這樣令人印象深刻,但仍然有差異。例如,我們執行的下一次運行對於 .NET Compact Framework 產生了 30 毫秒的載入時間,對於我們的 DLL 產生了 7 毫秒的載入時間。

只載入需要的資訊;扔掉不需要的資訊

良好的最佳化始於良好的記憶體管理。我們最喜歡的 .NET 編程方法是 Dispose。由於我們不是生活在擁有無窮記憶體的極樂世界中,因此在編寫代碼時考慮它所使用的資源是很重要的。對於每個表單,您都需要在啟用和停用時為其添加代碼,如下所示:

protected override void OnActivated(EventArgs e){    base.OnActivated (e);}protected override void OnDeactivate(EventArgs e){    base.OnDeactivate (e);}

在這些函數中,必須確保載入並丟棄對於應用程式而言不是非常關鍵的任何資訊。這裡是我們載入並丟棄我們的位元影像的地方。

同樣的思想也適用於您建立和使用的對象。當您不再使用對象並且可以釋放這些對象的任何時候,您都應當釋放它們。並且,所有對象都應當即時建立,除非對於應用程式的正常運行絕對必要。這包括表單本身。表單和控制項本身佔用記憶體。當您關閉表單時,也應該處置該表單。

C# 中的 using 關鍵字能夠很好地協助您進行處置。請看以下樣本:

using ( MyClass thing = new MyClass ()){    // Do something interesting here}

在大括弧內部,thing 對象有效。一旦程式控制離開大括弧,thing 的 Dispose 方法就被自動調用,並且 MyClass 的執行個體使用的任何資源都被釋放(儘管記憶體回收行程仍將負責釋放 thing 本身所佔用的記憶體)。當然,MyClass 對象必須支援 IDisposable 介面以使該機制工作。

可靠的應用程式體繫結構使該方案更加容易實現。一種編程範例稱為 Mediator Model。簡而言之,按照 Mediator Model 編寫的應用程式被進行了適當的安排,以便所有對象除了知道那些直接位於其下方的對象以外,不再知道任何對象。對象不知道它們的父物件,也不知道它們的同輩。與父物件之間的通訊是完全通過事件實現的,與直接後繼的通訊是使用方法實現的。通過遵守該模型,可以使在對象樹的整個部分不再使用時動態修剪它們變得非常容易,並且使記憶體管理變得更加容易。例如,讓我們假設您的應用程式建立了對象 A,而該對象本身又建立並使用了對象 B,後者又建立並使用了一個名為 C 對象。

當應用程式的這一部分不再需要時,頂層對象 A 只是調用 B.Dispose。然後,B.Dispose 調用 C 對象的 Dispose 方法。因此,從 A 往下的所有資源都被刪除。如果您最後又在其他應用程式中重用了該代碼,或者您在當前應用程式中四處移動它,則只要您堅持 Mediator Model 的規則,這些對象的初始化和清理工作就將很可靠。

除了使總體記憶體足跡保持較小以外,只載入所需資訊的另外一個理由是減少應用程式載入時間。所載入並緩衝的每個不需要的表單、位元影像或資料庫都會導致更長的載入時間。由於 WindowsCE 是即時啟動類型的作業系統,因此使用者將期望他們的應用程式具有相同的特徵。

線程可以協助您減少載入時間。可以使用它們建立不重要的結構,而讓主應用程式完成必需的活動,例如,檢查註冊、登入、顯示啟動畫面或者讓使用者瀏覽主畫面。如果程式具有很多可從工具列或菜單使用的功能,則可以線上程中載入可選的功能,並且增長或啟用菜單或工具列,以便隨著後台線程完成載入而顯示更多的功能。

將所有概念結合在一起 — TabControlEx 類

現在,應該將我們已經討論的很多概念結合在一起了。.NET Compact Framework 中的 TabControl 充滿了問題。最大的問題是所有頁和它們的控制項都在啟動時載入。這意味著在索引標籤控制項呈現之前存在較長的載入時間,並且會使用很多記憶體。我們已經編寫了一個新類 — 我們聰明地將其稱為 TabControlEx。它是一個從頭產生的索引標籤控制項,它使頁可以僅當使用者單擊該選項卡時才載入。TabControlEx 還使頁可以在使用者單擊另外一個選項卡時被丟棄。如果 Input Panel 出現或消失,這些頁將自動調整大小。還有其他一些很好的技巧,例如,對可見度進行更好的控制。有關與 TabControlEx 類相關聯的屬性和方法的列表,請參見圖 12

使用 TagControlEx 時,最大的區別是,您不再建立頁並將它們添加到集合中。相反,您將頁的類型添加到該集合中:

m_tab.TabPages.Add("Tab Text", typeof(MyPage));

TabControlEx 類具有一個名為 LoadPage 的私人方法,它使用該類型資訊建立控制項執行個體。LoadPage 方法的內部有負責建立實際對象的代碼:

private Control LoadPage(Type type, TabPageEx tab) {    ...    ConstructorInfo constructor = type.GetConstructor(new Type[] {});    Control page = (Control) constructor.Invoke(null);    ...    return page;}

使用反射獲得對預設建構函式的訪問並調用它,是一種通過一個程式碼片段即時建立不同種類對象的優雅方式。

LoadPage 方法包含一些代碼,這些代碼試圖從控制項中獲得一個 IPage 介面(請參見圖 13),然後調用 InitPage 方法:

IPage p = page as IPage;if (p != null) p.InitPage(this);

您實現 IPage 介面的原因有兩個。首先,您可能希望在載入需要與應用程式的其餘部分通訊的控制項之後,立即執行一些初始化工作。InitPage 方法具有單個參數 — 一個指向 TabControlEx 對象的指標。如果頁需要與選項卡上的其他頁通訊,則可以使用 TabPages 集合來尋找另一個控制項(如果它被載入的話)。您還可以註冊以接收 PageLoadedEventHandler 事件,以便您可以在其他控制項載入之後與它們互動。最後,您可以建立 TabControlEx 的子類,並使其具有一些額外的屬性或方法,以便您的頁用來與應用程式的其餘部分互動。

其次,並非所有頁都希望在輸入面板可見時調整大小。如果是這種情況,則您可以讓 IPage 介面的 CanResize 屬性返回 false。如果您為該屬性的實現返回 true,則將發生預設行為 — 調整頁的大小。

有時,您可能希望知道頁何時變為活動或非使用中,以便您可以完成某些工作,例如,基於應用程式中的其他更改更新值。.NET Compact Framework 中的 TabControl 沒有提供獲得這樣的通知的容易方式。通過實現只包含下列兩個方法的 IPageActivate 介面(請參見圖 13),可以非常容易地在頁控制項中獲得該通知:Activated 和 Deactivated。這些方法僅當控制項的特定執行個體被啟用或停用時才調用。使用 TabControlEx 的 SelectedIndexChanged 事件,可以為所有頁(而不是僅僅一個頁)獲得相同的資訊。通常,當頁需要知道它何時變為活動或非使用中時,使用 IPageActivate 更為容易。但是,當您希望監視應用程式中的所有頁時,SelectedIndexChanged 無疑更好。

在我們的 Test Bench 應用程式中,單擊“Tab Control”按鈕可顯示索引標籤控制項測試對話方塊。TabTest 對象的建構函式顯示在圖 14 中。

向 TabControlEx 中添加工具列按鈕

索引標籤頁面的最後一個可選介面提供了某種確實有用的功能,但是卻難以使用 .NET Compact Framework 中的索引標籤控制項實現。讓我們假設您具有一個功能表列,並且您希望具有一些特定於索引標籤控制項的每個頁的工具列。使用我們的 TabControlEx,這確實很簡單。您將需要向您的表單而不是 Toolbar 中添加 MyToolbar 的執行個體。MyToolbar 是 Toolbar 的子類,它支援工具列節的概念,即可以作為組顯示或隱藏的工具列按鈕。圖 13 中的 IPageToolbar 介面具有單個方法,該方法返回工具列節的列表。圖 15 顯示了我們的 Test Bench 應用程式範例中用於在螢幕上設定幾個工具列表徵圖的代碼。

IPageToolbar.Sections 方法中的代碼在該方法第一次調用時建立一個新的工具列節。然後,我們向該節中添加了兩個表徵圖並將其返回。LoadIcon 只是一個 Helper 函數,如下所示:

private Icon LoadIcon(string name){    string path = Path.Combine(Utility.HomePath, name);    usin(Stream stream = File.OpenRead(path))    {        return new Icon(stream);    }}

LoadIcon 獲得 Utility.HomePath 屬性,該屬性是用於封裝下面這團難以記憶的字元的單行方式:

public static string HomePath{    get     {         return Path.GetDirectoryName(            Assembly.GetExecutingAssembly().GetName().CodeBase);     }}

運行 Test Bench 樣本並選擇“Toolbar Test”按鈕。您可以在這兩個選項卡之間前後跳轉,並且查看工具列按鈕如何只為 Test 頁顯示。

您可能已經注意到了,對於這一帶有工具列的測試,以及未使用工具列的 Custom Control 測試,我們使用了相同的控制項 — MyCustomControl。如果 MyCustomControl 的 UseToolbar 屬性為 false(這是預設值),則 IPageToolbar.Sections 方法返回 null。為了針對工具列測試將該屬性設定為 true,我們使用了前面提到的用於在頁首次載入時與其進行通訊的技術。ToolbarTest 表單的建構函式與 TabTest 表單幾乎完全相同,不同之處在於在建構函式的結尾添加的額外代碼:

public ToolbarTest(){    ...    tab.PageLoaded += new         TabControlEx.PageLoadedEventHandler(tab_PageLoaded);        // Create an instance of our toolbar needed to allow     // each page to have its own toolbar buttons.    MyToolbar toolbar = new MyToolbar();    Controls.Add(toolbar);}

首先,我們進行註冊以便接收索引標籤控制項的 PageLoadedEventHandler 事件,該事件使我們可以在控制項載入之後立即設定控制項的屬性。我們將使用該事件將 UserToolbar 屬性設定為 true:

private void tab_PageLoaded(object sender, PageLoadedEventArgs e){    MyCustomControl control = e.Page as MyCustomControl;    if (control != null) control.UseToolbar = true;}

在建立事件處理常式之後,我們向表單中添加了 MyToolbar 的一個執行個體,它是 Toolbar 類的支援工具列節的子類。

小結

為 .NET Compact Framework 編寫應用程式是一種奇妙的經曆。這些應用程式足夠小,以至於共用軟體開發人員可以再一次自行編寫某種東西,並且以聯機方式銷售它來賺一點兒錢。但是,行動裝置的簡單性和較小的容量為程式員帶來了新的挑戰。您沒有令人敬畏的權力,卻具有令人恐懼的責任。請努力從一開始就設計快速、緊湊並且具有極高可處置性的代碼。

Dave Edson 當前是 Software Architect for Potala Software, LLC 的按摩治療專家、彈子機大王和軟體架構師。他與自己的妻子和兩個孩子一起生活在西雅圖。現在,他把所有可能的餘暇時間都花在滑雪和水上滑板運動上面。

John Socha-Leialoha 是 Potala Software, LLC 的首席軟體架構師。他過去的成就包括編寫 Norton Commander。他與自己的妻子和兒子一起生活在西雅圖。他把所有可能的餘暇時間都花在為他的鐵路模型愛好/業務建造小得可笑卻又極其精確的模擬模型上面。

 http://www.microsoft.com/china/MSDN/library/Mobility/pocketpc/NETCompactFramework.mspx?mfr=true

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.