程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 共享代碼:編寫同時適用於移動和桌面應用程序的代碼

共享代碼:編寫同時適用於移動和桌面應用程序的代碼

編輯:關於.NET

在過去幾年,盡管開發人員一直在為 Windows® 構建 Microsoft® .NET Framework 客戶端應用程序,但是許多人都不知道他們還可以使用相同的技能和工具集為 Windows Mobile® 創建應用程序。但是 Windows Mobile 當時還沒有在企業內廣泛使用,因此編寫面向移動設備的自定義應用程序的需求還不是很大。如今,為了滿足大量的需求,許多桌面開發人員開始涉足移動開發。遺憾的是,盡管跨平台共享 .NET 代碼相當容易,但許多這樣的機會都沒有得到利用。

無論原始應用程序是為 Windows 還是為 Windows Mobile 編寫的,有很多原因可能會促使您跨這兩種平台共享業務邏輯。您可以從其中任一個平台開始,遷移到另一個平台。如果應用程序當前是在現場的便攜式計算機上運行,則在移動設備上運行可能是個不錯的選擇,反之亦然。如果您的產品與競爭者的產品類似,通過將其擴展到新的平台,可以使您在競爭中占據優勢。還有一個額外的好處,就是通過編寫在桌面上執行的代碼,您可以利用不能用於智能設備項目的開發人員工具。

編寫面向具有不同外觀造型(不同的屏幕尺寸、方向、觸摸屏等)的 Windows 設備的跨設備代碼應用程序時,可以利用本文中介紹的技術和原理。

平台差異

當我想到 .NET Framework 時,我會想到工具、語言、庫和運行時引擎。這四種元素中的差異會影響編寫跨平台代碼的目標。幸運的是,我們對桌面和移動應用程序所使用的工具是相同的:Visual Studio®。對於 Visual Studio 的每個版本,Visual Studio for Devices 團隊繼續添加用於設備開發的新功能,並確保重要的新桌面開發功能對於移動設備開發也可以正常使用。(對於設備開發,您需要使用 Visual Studio 2005 Standard 或更高版本。)

從語法角度看,兩種平台都完全支持 Visual Basic® 和 C#。但是如果您計劃使用 Visual Basic,我們需要提醒您 Microsoft.VisualBasic.Compatibility 程序集和隱式後期綁定不受支持。前者是對從 Visual Basic 6.0 遷移的桌面項目使用的程序集;如果您通過在 Visual Basic 項目中啟用 Option Strict On 來遵循最佳實踐,後者不是問題。還要意識到托管 C++ 也不受支持(雖然相同的 IDE 可用於本機設備項目)。通過引用正確的程序集和確保其編譯器不生成 .NET Framework 精簡版公共語言運行庫 (CLR) 不支持的 IL 操作碼,第三方語言可以添加對 .NET Framework 精簡版的支持。沖突的 IL 操作碼包括:jmp、calli、refanyval、refanytype、mkrefany、arglist、localloc、unaligned 和 tail。

需要注意的是,盡管垃圾收集器和實時 (JIT) 編譯器的實現在 .NET Framework 精簡版中與在 .NET Framework 中不同,但是它們仍然起到相同的作用並為托管代碼提供相同的服務。

與任何主流編程框架一樣,.NET Framework 附帶有大量類庫。.NET Framework 精簡版僅占用了 20% 的資源就實現了 .NET Framework 中 80% 的相關功能。與 .NET Framework 相比,.NET Framework 精簡版中不包含的主要方面包括:ASP.NET、CLR 宿主、代碼訪問安全 (CAS)、二進制序列化、Reflection.Emit 以及 CodeDOM。

Windows Presentation Foundation 和 Windows Workflow Foundation 很快就將不受支持,但是 .NET Framework 精簡版 3.5 版中將提供 Windows Communication Foundation 的精簡版。在 3.5 版本中,還將支持 LINQ to Objects、LINQ to XML 以及 LINQ to DataSet,但是不支持任何其他 LINQ 類型。

然而,大多數情況下,並不是因為缺少這些大的方面而導致出現問題。通常,問題是由於某些受支持的命名空間不具備您所需的所有類引起的,更重要的是,某些類不具備您所需的所有成員。稍後,我將介紹一些解決方法。

反之也是如此。也就是說,.NET Framework 精簡版從嚴格意義上說不是 .NET Framework 的子集;它實際上添加了一些自己的成員。有四種特定於設備的程序集:System.Data.SqlServerCe、System.Net.IrDA、Microsoft.WindowsMobile.DirectX 以及 Microsoft.WindowsCE.Forms。

SqlServerCe 包含的類用於處理設備上的內存數據庫;IrDA 包含的類用於針對紅外線進行編程;DirectX® 用於處理豐富圖形,主要適用於游戲編程;Forms 擁有大量特定於設備的 GUI 類。除了這些特定於設備的程序集之外,還有一些適用於 Windows Mobile 5.0 和更高版本的特定於設備的 Windows Mobile API,它們隨 Mobile 平台一起提供。稍後,我將向您介紹如何處理在一種框架中存在,在另一種框架中不存在的類。

當然,桌面和移動設備上的輸入方法是不同的,因此,您不必擔心移動設備上的鍵盤快捷方式、工具提示等。此外,移動窗體不能調整大小,始終是全屏,這也會影響您在應用程序的窗體間導航的方式。最後,最明顯的差別是設備的屏幕尺寸要小得多。為了消除這種影響,最好不要在這兩種平台間共享應用程序的 UI 層,而是創建特定於平台的用戶界面,僅共享業務邏輯。

如果無法繼續進行怎麼辦

嘗試將應用程序遷移到移動設備時,桌面開發人員通常做的第一件事是根據桌面框架構建程序集並從設備項目中引用該程序集。這樣做是行不通的,絕對不支持這樣做。即使在桌面項目中,您已經很小心,只使用 .NET Framework 精簡版中也提供的類和成員,在運行時依然會出現 TypeLoadException 和 MissingMethodException 異常。因此,如果您通過選擇非智能設備項目模板創建了 Visual Studio 項目,該項目的輸出簡直不能在 Windows CE 平台(當然,這是 Windows Mobile 的基礎)上執行。

遺憾的是,您有時就面臨上述情形。從智能設備項目引用程序集時,不會檢查它是不是桌面程序集,因此您可以使用從桌面項目模板中構建的類庫。當使用第三方 DLL 時,經常出現這種情況。在這種情況下,您只能到運行時才會發現錯誤。

如果您懷疑遇到了這一問題,有一些線索可供查找問題。通過 Visual Studio 將項目部署到目標位置時(如通過調試運行項目時),請注意在 Visual Studio 輸出窗口中,診斷消息會告知您哪些程序集正在部署到設備(請參見圖 1)。如果部署過程花費的時間很長,可能是因為 Visual Studio 正在將它檢測為您意外引用的桌面程序集依賴關系的全套 .NET Framework 程序集復制到目標位置。

圖 1 Visual Studio 輸出窗口

如果您幸運的話,部署過程在失敗時會顯示通知您磁盤空間不足的錯誤。如果桌面依賴關系有限,則部署過程會成功。在這種情況下,可以使用顯示所有文件類型的遠程文件查看器,查看部署應用程序的目標位置上的文件夾。您將看到所有部署的 .NET Framework 程序集。注意,.NET Framework 精簡版程序集的公鑰令牌以 9 開頭,而桌面的則以 B 開頭。您可以使用這一信息來快速確定您所部署或引用的程序集是桌面還是設備框架程序集。該信息還會出現在 .NET Framework 精簡版加載程序日志中,如果應用程序因為這些問題在運行時失敗,該日志中將包含錯誤。

可重定目標

盡管桌面程序集不能在設備上執行,但好消息是:反之是可以的。如果您創建了智能設備項目並對其進行了構建,則輸出(EXE 或 DLL)可以在桌面上執行,並且可以由桌面項目引用。這是因為 .NET Framework 精簡版程序集可重定目標。如果您使用 ILDASM 打開其中一個程序集,您可以觀察到它們具有 System.Reflection.AssemblyNameFlags.Retargetable 屬性(請參見圖 2)。這意味著在運行時所有對 .NET Framework 精簡版程序集的引用都可以重定目標到桌面程序集。因此,需要說明的是,您的程序集使用的是 .NET Framework 的桌面實現,而不是設備實現。

圖 2 ILDASM 中的可重定目標標志

讓我們看一個您可以輕松嘗試的示例。創建一個面向您最喜歡的 Windows Mobile 平台的新智能設備應用程序。向主窗體添加一些控件,例如 TabControl、ProgressBar,並向 MainMenu 控件添加四個 MenuItem(請參見圖 3)。對每個 MenuItem 生成 Click 事件處理程序方法,並在這些事件處理程序方法中添加圖 4 中的代碼。

Figure 4 Click 事件處理程序方法

private void mnuFormShow_Click(object sender, EventArgs e)
{
 // TODO later
}
private void mnuMbox_Click(object sender, EventArgs e)
{
 MessageBox.Show(“some text”, “random caption”);
}
private void mnuDateTime_Click(object sender, EventArgs e)
{
 this.Text = DateTime.Now.TimeOfDay.ToString();
}
private void mnuCalc_Click(object sender, EventArgs e)
{
 string pth = @”\Windows\calc.exe”;
 Process.Start(pth, null);
}

圖 3 MainMenu 設計

運行面向仿真器或您自己的設備的項目,並觀察在單擊每個菜單項時的行為是否與您期望的一樣。返回到桌面,導航到構建文件夾 (bin/Debug),注意觀察雙擊可執行文件後其在桌面計算機上的運行情況(請參見圖 5)。

圖 5 設備項目

各控件按照桌面樣式呈現 — TabControl 在頂部顯示選項卡,菜單出現在頂部,ProgressBar 的外觀略有不同,等等。現在單擊顯示 MessageBox 的 MenuItem,它按預期方式運行。單擊更改窗體標題的 MenuItem,注意我們如何通過添加毫秒在此處獲得更高精確度。這也可以進一步說明您現在使用的是有一點點不同的桌面實現。至此的結論是不要完全依賴於可重定目標性,而是要全面測試應用程序以確定實現中的任何微小差別對您都不太重要。最後,單擊啟動計算器的 MenuItem 並觀察應用程序如何崩潰,並顯示“System.ComponentModel.Win32Exception:系統找不到指定的文件”異常。顯然,出現這種情況的主要原因是桌面上的計算器路徑與設備上的計算器路徑不同。要解決此類特定於平台的問題並提供可以跨平台作業的單一實現,您需要在運行時檢測所在的平台並相應地編寫代碼的分支。請將以下對事件處理程序所做的修改作為參考示例:

private void mnuCalc_Click(
object sender, EventArgs e)
{
string pth;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
pth = @”\Windows\calc.exe”;
}
else
{
pth = @”c:\Windows\system32\calc.exe”;
}
Process.Start(pth, null);
}

您現在可以在 Windows Mobile 或 Windows 桌面上運行該項目,對應於該平台的計算器應用程序將可以正確啟動。

請記住,有一些特定於設備的程序集在桌面上不存在。讓我們看一下使用此類程序集將發生什麼情況。向智能設備項目添加一個新窗體 (Form2),並在其中添加 InputPanel 控件,然後添加兩個 Button 控件和一個 TextBox。對控件生成事件處理程序並添加圖 6 中所示的代碼。

Figure 6 textBox 事件處理程序

private void textBox1_GotFocus(object sender, EventArgs e)
{
 inputPanel1.Enabled = true;
}
private void textBox1_LostFocus(object sender, EventArgs e)
{
 inputPanel1.Enabled = false;
}
private void button1_Click(object sender, EventArgs e)
{
 Cursor.Current = Cursors.WaitCursor;
}
private void button2_Click(object sender, EventArgs e)
{
 Cursor.Current = Cursors.Default;
}

現在重新訪問第一個窗體的空 MenuItem Click 事件處理程序方法並添加顯示 Form2 的代碼,如下所示:

private void mnuFormShow_Click(object sender, EventArgs e)
{
 Form2 f = new Form2();
 f.ShowDialog();
}

如果該在設備上運行項目,您會注意到從 Form1 的菜單項打開 Form2 時,您可以單擊 Form2 的按鈕以在設備上顯示或隱藏忙狀態的光標。將焦點轉移到 Textbox 將打開軟輸入面板 (SIP),而將焦點移走會隱藏該面板。返回到桌面,像前面一樣導航到構建目錄,並在桌面上運行可執行文件。單擊顯示 Form2 的菜單項將導致出現異常,如下所示:

System.IO.FileNotFoundException: Could not load file or assembly
‘Microsoft.WindowsCE.Forms, Version=2.0.0.0, Culture=neutral,
 
PublicKeyToken=969db8053d3322ac’ or one of its dependencies.
The system cannot find the file specified. File name: ‘Microsoft.WindowsCE.Forms,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=969db8053d3322ac’

發生這種情況,是因為向項目添加 InputPanel 控件時,會自動添加對 Microsoft.WindowsCE.Forms 的引用。它是僅設備程序集,所以在重定位到的桌面上並不存在,因此在構建窗體並嘗試從該程序集創建 Microsoft.WindowsCE.Forms.InputPanel 時將引發異常。讓我們做一些修改,以更好地進行觀察。首先從該窗體的設計器視圖刪除 InputPanel 控件,然後修改 textBox 事件處理程序,如圖 7 所示。

Figure 7 檢查平台

private void textBox1_GotFocus(object sender, EventArgs e)
{
 if (Environment.OSVersion.Platform == PlatformID.WinCE) //naive
 {
  Microsoft.WindowsCE.Forms.InputPanel inputPanel1 =
        new Microsoft.WindowsCE.Forms.InputPanel();
  inputPanel1.Enabled = true;
 }
}
private void textBox1_LostFocus(object sender, EventArgs e)
{
 // in the interest of space, let’s ignore this method
 //inputPanel1.Enabled = true;
}

此操作所做的是將 InputPanel 的使用分離到一種方法中,並且它還通過在運行時檢查平台嘗試在本地修復異常如果您在設備上運行此項目,它仍然可以按照預期的方式運行,但是如果在桌面上運行,它會失敗,並發生與前面相同的錯誤。它僅在 textBox1 獲得焦點時失敗,而不是一顯示窗體就會失敗,但是無論如何它終究會失敗。

由於 JIT 編譯,僅進行運行時檢查在這種情況下是不夠的。在運行時調用方法時,CLR 會檢查該方法的現有任何本地代碼是否正在被調用 (textBox1_GotFocus)。如果沒有,則 JIT 編譯器將從此方法入口指向的 IL 代碼生成本地操作碼。此時,JIT 編譯器不管在運行時從什麼路徑執行,都只需要查找此方法中使用的所有類型。因此,它嘗試加載 Microsoft.WindowsCE.Forms.InputPanel 類型和對應的程序集,但是卻無法完成,因為程序集在桌面上不存在。然後就引發了異常。您可以將對 InputPanel 的使用提取到一個單獨的方法中,並通過對調用方法的運行時檢查確保提取的方法永遠不被調用,但是這種方法實在比較亂,大多數開發人員都不喜歡。解決此問題的正確方式是探求其他方法跨平台共享代碼。(如果您對上面提到的方法感興趣,請查看 msdn2.microsoft.com/ aa480686 上的“平台檢測”部分,其中提供了一個與上述方法相似的分解方法的示例。)

共享代碼(而非二進制文件)

上一部分中說明了如何創建智能設備項目以及如何通過重定位在桌面上使用輸出二進制文件。然而,可重定位目標方法有兩個限制。首先,在代碼中不能使用任何僅適用於桌面的類型或方法。它們無法進行編譯。第二,必須小心不能在桌面上加載任何特定於設備的程序集,因為它們會因出現運行時異常而失敗。

這兩個問題都可以通過以下方式解決:在編譯時而非運行時使用代碼的條件分支,創建兩個項目,使其具有兩個單獨輸出以便獲得使用一個源代碼文件集的特定於平台的程序集。後者很重要,因為最終目標是將對業務邏輯的所有更改同時傳播給兩個平台,而不產生任何易於出現復制/粘貼錯誤的開銷。讓我們嘗試一下另一種方法。

首先,創建一個新 Windows 桌面項目。刪除默認添加的代碼文件(Form1 和 Program 文件)。從項目菜單中選擇“添加現有項...”,並浏覽到以前創建的智能設備項目文件夾,代碼文件就駐留在其中。最後,僅選擇所有文件名形式為 *.cs 的代碼文件並將其作為鏈接添加到項目。這很重要,因為如果選擇對話框的默認“添加”選項,您將會創建副本而不是鏈接到這些文件(請參見圖 8)。

圖 8 添加現有文件

構建項目,注意在編譯時現在會出現以前僅在運行時才出現的錯誤(請參見圖 9)。當然,在這種情況下添加對特定於設備的程序集的引用沒有用。因此,我將在此介紹條件編譯。

圖 9 在編譯時發現問題(單擊該圖像獲得較大視圖)

轉到桌面項目屬性,在“構建”選項卡上添加名為 FULL_FRAME(即“full framework”的簡略形式)的新“條件編譯符號”,如圖 10 所示。

圖 10 條件編譯常量

接下來,使用新引入的編譯常量修正 Form2.cs 中的編譯錯誤。通過以下方法修改沖突方法,然後重新構建桌面項目:

private void textBox1_GotFocus(object sender, EventArgs e)
{
#if !FULL_FRAME
  Microsoft.WindowsCE.Forms.InputPanel inputPanel1 =
     new Microsoft.WindowsCE.Forms.InputPanel();
  inputPanel1.Enabled = true;
#endif
}

構建此(桌面)項目後,您可以運行該項目,查看是否一切都按照預期的方式工作。如果切換到設備項目,請注意該項目如何提示您源文件已在 Visual Studio 實例外部發生更改並詢問您是否要重新對其進行加載;單擊“是”。

因為這是一個使用單獨條件編譯常量集的單獨項目,所以該項目將可以在設備上正常編譯和運行 — 您不再需要在桌面上運行此項目的輸出!這意味著您可以更改以前在啟動計算器時引入的運行時條件,使其成為編譯時條件,從而減小方法的大小。

現在,您已經了解了使用條件編譯的機制,讓我們來思考一下它的好處。擁有兩個單獨的項目意味著您在每個項目中可以具有不同的項目引用集。您還可以在一個項目中包含代碼文件,在另一個中不包含。在桌面端,您可以利用完整框架的成員來為桌面用戶創建良好的體驗;只是要確保對這些桌面成員的使用要以 #if 處理器指令結束,或者分離到僅在桌面項目中存在的源文件中。在很多示例中,您可以使用完整框架成員增強在桌面端的體驗,我們來看一個示例。

在 Program.cs 文件中,按照以下方式修改 Main 函數:

static void Main()
{
#if FULL_FRAME
 Application.EnableVisualStyles();
#endif
 Application.Run(new Form1());
}

現在運行項目,將原始控件的外觀與通過可重定位目標方法得到的控件外觀進行比較(請參見圖 11)。

最後,敏銳的您會發現顯示和隱藏光標的代碼在桌面上不能運行(即使不會出現任何編譯或運行時錯誤)。還要注意,從 Visual Studio 2005 開始,設備項目已具有一些已經定義的條件編譯常量:分別適用於 PocketPC、Smartphone、Windows CE。您可以選擇僅使用內置的常量,也可以選擇使用自定義的常量(如剛剛介紹的 FULL_FRAME),還可以同時使用內置常量和自定義常量 — 這一點關系都沒有。

圖 11 原始控件和重定位控件

了解了跨平台共享代碼資源後,應該了解一些提示和技巧。其中一些適用於可重定位目標方法,一些適用於條件編譯,還有一些對兩者都適用。

同一目錄中的項目和代碼文件

共享代碼時,我創建了兩個單獨的項目,並且以鏈接形式將第一個項目的源代碼文件添加到了第二個中。我建議將所有的源代碼文件與項目文件放在同一目錄中,這樣可以根據需要很輕松地在 Visual Studio 解決方案浏覽器中包含或排除這些文件。為了實現這個目標,在創建設備項目後,創建一個 Windows 桌面項目並像前面一樣刪除代碼文件。不要再添加任何文件,關閉 Visual Studio 並導航到創建桌面項目文件的目錄。剪切項目文件並將其粘貼到設備項目文件所在的目錄。例如,如果設備項目位於“C:\Temp\”,則將桌面項目文件 (csproj) 剪切並粘貼到該文件夾(以確保桌面和設備項目具有不同的名稱)。

現在打開桌面項目並在解決方案浏覽器中“顯示所有文件”。您可以看到所有設備源代碼文件,並將其納入您認為適用的項目中 — 如果您打開設備項目,也是一樣(請參見圖 12)。

圖 12 選擇文件

通過這種方法,向一個項目添加新文件時,即使打開的是其他項目類型也可以很輕松地找到,並且可以更簡單地包含所需的文件。

請注意,項目和輸出程序集名稱是不同的,但是命名空間和類型名稱應保持相同。如果您希望兩個平台使用相同的程序集名稱,則需要在項目屬性中重命名構建文件夾,例如 binCF 和 binFF。我還建議任何時候都只打開一個項目,這樣可以避免產生混淆,特別適合源代碼管理。

為代碼添加存根

很多時候,最好不要使用條件編譯使代碼顯得雜亂,而應該對不適用於平台的代碼添加虛擬空存根。例如,如果您在項目中總是使用某個類,並且該類不能在其他平台上使用,則您可以使用相同的(但是為空)的接口添加將該類。下面是使用 InputPanel 類的一個示例:

namespace Microsoft.WindowsCE.Forms
{
  public class InputPanel
  {
    public bool Enabled;
  }
}

現在,在任何位置使用 InputPanel,都可以使用相同的代碼而不需要條件分支,這對桌面平台沒有任何影響。

平台調用

很多 .NET Framework 精簡版應用程序都可以通過使用 P/Invoke 服務訪問駐留在本機 DLL 中的功能,從而實現其他功能。通常,這些 DLL 的名稱在 Windows CE 和桌面版本的 Windows 上不同。根據前面您選擇的方法,可以通過兩種方法處理。如果使用的是條件編譯,圖 13 中的代碼說明了如何編寫跨平台的 P/Invoke 聲明。如果選擇的是可重定目標方法,圖 14 中的方法很有用。

Figure 14 運行時 P/Invoke 決策

class PInvokesRuntime
{
[DllImport(“coredll.dll”, EntryPoint = “QueryPerformanceCounter”)]
internal static extern int QueryPerformanceCounterCE(
out Int64 perfCounter);
[DllImport(“kernel32.dll”, EntryPoint = “QueryPerformanceCounter”)]
internal static extern int QueryPerformanceCounterFull(
out Int64 perfCounter);
private void UseIt()
{
long p;
int i;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
i = QueryPerformanceCounterCE(out p);
}
else
{
i = QueryPerformanceCounterFull(out p);
}
}
}

Figure 13 編譯時 P/Invoke 決策

class PInvokesCompileTime
{
#if FULL_FRAME
private const string DllName = “kernel32.dll”;
#else
private const string DllName = “coredll.dll”;
#endif
[DllImport(DllName)]
internal static extern int QueryPerformanceCounter(
out Int64 perfCounter);
private void UseIt()
{
long p;
int i;
i = QueryPerformanceCounter(out p);
}
}

不同的框架實現

在前面的部分,您了解了 System.DateTime.TimeOfDay 如何根據使用的框架返回不同精確度的值。因此,您可以在測試之後決定如何在代碼中進行相關處理(如果這對您來說很重要)。在某些其他情況下,各個方法具有不同的行為,尤其是在特定情況下,一個框架中的方法引發的異常會不同於另一個框架中的方法引發的異常,例如:

private void button1_Click(object sender, EventArgs e)
{
Type t = this.GetType();
MethodInfo m = t.GetMethod(“DoIt”);
m.Invoke(this, null); //throws
}
public void DoIt()
{
throw new InvalidOperationException(“my message”);
}

此代碼中調用 MethodInfo 的一行在兩個平台上都會引發異常。在設備上,引發的異常是 InvalidOperationException。然而,在桌面上引發的異常卻是 TargetInvocationException(它將原始的 InvalidOperationException 集設置為 InnerException 屬性)。因此,在測試過程中,您還應該測試異常情況,確保兩種 Exception 類型中都有 Catch 塊。

不同框架實現的另一個示例是在前面顯示忙狀態的光標時您看到的內容。對設備有影響的東西對桌面沒有任何影響,桌面上需要的東西在設備上不受支持。如果您希望兩個平台上的行為一致,可以提取一種方法,並在其中隔離封裝光標顯示的條件行為,如下所示:

public static class TheCursor
{
public static void CursorCurrent(Control c, Cursor defaultOrWait)
{
#if FULL_FRAME
if (c != null) c.Cursor = defaultOrWait;
#else
Cursor.Current = defaultOrWait;
#endif
}
}

然後設備和桌面平台上使用的調用代碼可以保持相同:

private void button1_Click(object sender, EventArgs e)
{
TheCursor.CursorCurrent(this, Cursors.WaitCursor);
}
private void button2_Click(object sender, EventArgs e)
{
TheCursor.CursorCurrent(this, Cursors.Default);
}

分部類型

在前面我提到過,如果源代碼文件包含特定於平台的功能,您可以在一個項目中包含它們,而在另一個項目中不包含。在類適用於兩種平台但是類的某些成員不適用於這兩種平台時,會出現相似的情況。您不需要通過 #if 指令封裝類的大部分,您可以使用 Visual Studio 2005 中引入的分部類功能。換句話說,就是使用局部關鍵字將類拆分為兩個文件,以此將只適用於一個平台的功能分離到一個不同的文件中。然後只需在不適合的項目中排除該文件即可。

System.Diagnostics.Conditional

值得注意的一個提示是可以對不希望在運行時調用的方法使用 System.Diagnostics.ConditionalAttribute。對方法應用該屬性的結果是任何對該方法的調用都不會包含在經過編譯的二進制文件中。當然,應用了該屬性的方法必須仍然能夠編譯:

[Conditional(“FULL_FRAME”)]
public void SomeDesktopOnlyMethod()
{
//TODO code that compiles in both projects
}

如果以 #if 結束整個方法,您必須查找所有調用該方法的位置,並使用 #if 封裝調用。而此處,只需對不需要的方法應用該屬性,而不用管所有調用的位置;這樣就不會在二進制文件中對其進行編譯。

部署到我的計算機

我們都知道,如果選擇可重定目標方法,在運行時只能發現桌面平台錯誤。這就產生了如何在桌面上調試應用程序的問題,因為智能設備項目只能部署到仿真器或物理設備。您可以采取兩種方法。

最簡單但也是最乏味的方法是從文件系統運行該應用程序,然後通過“Visual Studio 工具”|“附加到進程”菜單對其附加調試程序(請參閱圖 15)。您還可以通過創建桌面項目充當啟動程序來半自動化此過程。向具有智能設備項目的解決方案添加桌面項目,將其設置為啟動項目,然後引用此智能設備項目。確保該桌面項目只有一個程序文件且該文件只具有一個靜態 Main 函數,並通過該函數將智能設備項目的啟動窗體的一個新實例傳遞給 Application.Run 方法。

圖 15 “附加到進程”對話框

如果智能設備項目的部署選項中具有“部署到我的計算機選項”,將是很理想的情況。事實上,在早期的 Community Technology Preview of Visual Studio 2005 期間,存在該選項(但是最終產品中刪除了該選項,Visual Studio“Orcas”版本中也沒有重新添加該選項)。

按照我的博客中的說明進行操作可以重新啟用“部署到我的計算機”功能,但是請注意 Microsoft 不建議(或支持)這樣做。

使用映像

如果選擇使用可重定目標方法,則不能使用僅適用於桌面的方法,因為這些方法在設備項目中將無法編譯。然而,如果確實需要,可以使用映像調用此類方法。重新回顧以前我希望為應用程序啟用主題的示例,我可以通過以下方法實現:

static void Main()
{
//Application.EnableVisualStyles();
if (Environment.OSVersion.Platform != PlatformID.WinCE)
{
Type t = typeof(Application);
MethodInfo m = t.GetMethod(“EnableVisualStyles”);
m.Invoke(null, null);
}
// As before
Application.Run(new Form1());
}

擴展方法

在使用 .NET Framework 精簡版類時,經常會出現這樣的情況:與完整 Framework 相比,特定類缺少成員。通常,開發人員必須自己編寫缺少的功能,然後決定是使用繼承添加缺少的成員(如果可以),還是將其添加到實用程序幫助器類。例如,假設您要對與桌面共享的設備項目中的 TextBox 執行 Cut 操作。但 .NET Framework 精簡版中的 TextBox 類中缺少 Cut 方法。根據在本文中學習的內容,您可以向設備項目添加類似圖 16 所示的代碼文件。

Figure 16 TextBoxExtensions

using System;
using System.Windows.Forms;
using Microsoft.WindowsCE.Forms;
static class TextBoxExtensions
{
const int WM_CUT = 0x0300;
const int WM_COPY = 0x0301;
const int WM_PASTE = 0x0302;
public static void Cut(TextBox t)
{
Message msg = Message.Create(
t.Handle, WM_CUT, IntPtr.Zero, IntPtr.Zero);
MessageWindow.SendMessage(ref msg);
}
}

圖 16 中的文件不需要條件,因為它只呈現在設備項目中。然後您可以按照如下所示的內容編寫調用代碼(在兩個項目中都將存在):

private void SomeMethod()
{
#if FULL_FRAME
textBox1.Cut();
#else
TextBoxExtensions.Cut(textBox1);
#endif
}

C# 3.0 和即將發布的 Visual Basic 版本中有一個稱為擴展方法的新功能。在此,我不對此功能進行全面地說明,不過,如果您對此功能不熟悉,可以訪問我的博客或查看 Anson Horton 的文章(發表在《MSDN? 雜志》2007 年 6 月這一期上)。代號為“Orcas”的 Visual Studio 中隨附的 .NET Framework 精簡版 3.5 也支持此功能,該功能可以幫助解決上述情況。

在設備項目中,添加對 System.Core.dll 的引用,然後通過在幫助器方法的第一個參數前添加 this 關鍵字修改僅設備文件,以此使該方法成為擴展方法,如下所示:

public static void Cut(this TextBox t)

完成此更改後,調用代碼在設備和桌面平台上可以相同,而且沒有條件:

private void SomeMethod()
{
textBox1.Cut();
}

在桌面上,將調用框架方法;在設備上,將調用擴展方法。如果將來 .NET Framework 精簡版團隊添加了此方法,則代碼將無縫調用真正的 .NET Framework 方法(因為實例方法優先於擴展方法),在某個時候您可以刪除多余的代碼文件。

總結

在本文中我介紹了桌面開發和 Windows Mobile 開發之間的差異,總結出最重要的差異是在 Framework 庫中,而不在其他地方。我提供了兩個用於跨平台共享代碼資源的方法示例,並表明我建議使用條件編譯方法,此外還提出了一些有用的技巧。盡管我個人建議使用此知識共享類庫中包含的業務邏輯,您可能還是希望共享 GUI,特別是在應用程序面向設備時,如 Ultra Mobile PC (UMPC) 或其他運行 Windows 桌面(而不是面向 Windows Mobile)的觸摸屏設備)。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved