第一支 .NET MAUI 程式 (for Windows)

基於學習的目的,我開始用 MAUI 來寫一些執行於 Windows 平台的小工具。這篇筆記會包含兩個入門的小練習,並解說 MAUI 專案的檔案結構。


內容大綱:
  1. 開發環境
  2. 第一支 MAUI 程式
  3. 了解 MAUI 專案結構
  4. MainPage
  5. 練習一:增加一個按鈕並撰寫事件處理常式
  6. 練習二:修改視窗大小與位置(限定 Windows 平台)
  7. 練習三:開啟新視窗(跨平台,但通常只用於 Windows)
  8. 練習四:用 Visual Live Tree 觀察視窗內部結構
  9. 結語

開發環境

預設情況下,Windows 會防止使用者執行那些沒有簽章的應用程式。因此,我們必須開啟 Windows 的「開發人員模式」,以便在本機開發和除錯 .NET MAUI 程式。



有關此設定的操作步驟亦可參考微軟文件:Deploy and debug your .NET MAUI app on Windows


接著便可以開始寫第一支 MAUI 程式,我用的工具是 Visual Studio 2022 預覽版 17.4.0 Preview 5 。應用程式的 Target Framework 是 .NET 7,我的電腦上安裝的 .NET 版本是 7.0.100-rc.2.22477.23。

第一支 MAUI 程式

從 Visual Studio 建立一個新專案,專案範本選擇「.NET MAUI App」。專案建立完成後,先執行看看應用程式長什麼樣子。詳細的操作步驟可參考微軟文件:Build your first app。下圖是在我的機器上執行的結果:



註:畫面中間的 .NET 吉祥物圖案是我改過的。你可以點此連結訂製吉祥物,然後下載製作好的圖片。稍後會說明如何置換此圖片。

到 Solution Explorer 視窗裡面查看專案中有哪些程式檔案,然後在應用程式執行的狀態下,按以下步驟修改程式碼:

  1. 開啟 MainPage.xaml。
  2. 找到 Label 元素,把 Text 屬性值從原本的 "Hello, World!" 改為 "我的第一支 MAUI 程式!"。
  3. 切換至執行中的應用程式,此時應該會看到視窗中已經出現剛才輸入的文字。此步驟可觀察 Visual Studio 的 Hot Reload 功能是否正常運作。
  4. 把自製的 .NET 吉祥物圖檔以滑鼠拖曳至 Solution Explorer 的 Resources\Images 資料夾(也可以用加入檔案的方式),並查看 Properties 視窗以確認該檔案的 Build Action 是「MauiImage」。


  5. 回到 MainPage.xaml,找到 Image 元素,把 Source 屬性從原本的 "dotnet_bot.png" 改為自製的吉祥物圖片檔名,然後存檔。如果應用程式仍在執行狀態,而且 Hot Reload 功能運作正常,此時應該會看到應用程式視窗中的吉祥物圖案被換掉了。

註:Hot Reload 是 .NET 的一項功能,它能讓我們在應用程式執行時一邊修改程式,一邊立刻看到修改的結果。

了解 MAUI 專案結構

在 Solution Explorer 中,可以從 .NET MAUI 專案的根目錄底下看到四個檔案,分別是 App.xaml、AppShell.xaml、MainPage.xaml、和 MauiProgram.cs,如下圖:



接著說明這四個檔案之間的關係。


首先要看的是 MauiProgram.cs,裡面有應用程式的進入點,如下圖:



上圖的第 11 行,也就是 UseMauiApp<App>(),其作用是註冊此應用程式,而應用程式的類別名稱是 "App"。這個 App 類別就是專案根目錄下的 App.xaml 以及與之關聯的 C# 檔案 App.xaml.cs。下圖即為 App.xaml.cs 的內容:



第 9 行指定了此應用程式的主頁是 AppShell,此類別即定義於專案根目錄下的 AppShell.xaml 以及與之關聯的 AppShell.xaml.cs。下圖為 AppShell.xaml 的內容:



顧名思義,AppShell 就是應用程式的「外殼」,它定義了應用程式的起始畫面。如上圖所示,ShellContent 元素定義了主頁是由 MainPage 類別負責,也就是專案根目錄下的 MainPage.xaml 和 MainPage.xaml.cs。


到目前為止,我們從應用程式進入點 MauiProgram.cs 開始順藤摸瓜,依序看了 App.xaml、AppShell.xaml,以即 MainPage.xaml。這四個檔案之間的順序和關聯,應該大致清楚了。



接下來,可以花更多時間看一下 MainPage.xaml 和它的 C# 檔案 MainPage.xaml.cs。

MainPage

基本上,.xaml 檔案是用來放 UI 元素,並設定元素的屬性值,而 C# 檔案則用來撰寫 UI 的互動邏輯。比如說,按鈕按下的事件處理常式,我們可以在 MainPage.xaml 裡面看到 CounterBtn 這個按鈕的元素的 Clicked 屬性被設定成 "OnCounterClicked":



OnCounterClicked 是一個函式,寫在 MainPage.xaml.cs 裡面,如下圖。



其中比較特別的是第 21 行,呼叫 SemanticScreenReader.Announce() 的作用是讓螢幕報讀軟體讀出傳入該函式的文字,以便視障者透過聽覺來了解目前的狀況。如果把這行拿掉,那麼當按鈕按下時,即使目前的 Windows 環境有啟動語音報讀軟體,也不會報讀 CounterBtn 按鈕上面的文字,而必須把輸入焦點移動至那個按鈕,報讀軟體才會讀出按鈕文字。


👉 參考微軟文件:使用語意屬性建置無障礙應用程式

練習一:增加一個按鈕並撰寫事件處理常式

接著來做一個小練習:在 MainPage 上面增加一個按鈕,並且在按鈕按下時彈出一個對話窗來顯示當前的日期時間。操作步驟可參考以下短片,就不寫成文字了(亦可至 Youtube 觀看)。



練習二:修改視窗大小與位置

我在寫這個小程式的時候,是以 Windows 平台為目標,並沒有打算執行於 Android 或 iOS 行動裝置,所以有一些想要嘗試的點子是來自以往開發 Windows Forms 程式的經驗。比如說,在 Windows Forms 應用程式中,如果想要在主視窗載入時設定視窗大小,只要在 Form 的 Load 事件處理常式中設定 Width 和 Height 屬性就行了。然而,這件小事在 MAUI 應用程式當中並沒有我想像的那麼容易。


起初,我是在 stackoverflow 找到解法,帖子標題是:MAUI .NET Set Window Size。亦可參考以下截圖:




此解法需要 12 行程式碼,還得動用 Win32 API,透過視窗代碼(handle)來達成目的。我想應該沒有人喜歡這種寫法。

之後,我上網爬了一下,發現原來 GitHub 上面的 MAUI 專案已經有 ticket:Enable Window Sizing,相關功能已經實作完成且合併至主分支。我參考其中的說明,試出以下程式碼可以達到我想要的效果,也就是在主視窗開啟的時候自動設定視窗大小,並顯示於螢幕的正中央。


雖然程式碼看似比先前那個使用 Win32 API 的寫法還要長,但那是因為這次還加入了「把視窗移動至螢幕中央」的處理。我選擇在視窗獲得輸入焦點的時候(Activated 事件)來改變視窗大小和位置。視窗物件還有哪些事件,也可順便了解一下:.NET MAUI 應用程式週期
要特別提出來說明的是第 30 行程式碼,也就是呼叫視窗物件的 Dispatcher.DispatchAsync() 方法。如果刪除這行程式碼,那麼第 33 行的 MainDisplayInfo 將在某些時刻無法取得主螢幕資訊:螢幕解析度的 Width 和 Height 都會是 0,這將導致後續程式碼無法將視窗位置移至螢幕中央。

以上程式碼僅適用於 Windows 平台。如果是 Mac,解法較不直觀,需要設定 Window 物件的 MinimumWidthMaximumWidthMinimumHeightMaximumHeight 這四個屬性,詳情參見剛才提及的那個 ticket

練習三:開啟新視窗

上一個練習的目的是讓應用程式的「主視窗」開啟時能夠設定視窗的大小和位置,其程式寫法只對應用程式的主視窗有作用。如果主視窗載入之後需要開啟另一個新視窗,那個新視窗的大小和位置就很容易設定了。以下便是開啟新視窗的練習步驟。

步驟 1:加入一個新頁

在剛才練習的專案中加入一個新的 Content Page:在 Solution Explorer 的專案名稱節點上點滑鼠右鍵,選擇 Add > New Item,然後選擇 .NET MAUI ContentPage (XAML),檔案名稱採用預設的 NewPage1.xaml。如下圖:


此新頁面不用加入任何控制項,我們只是要拿它來練習開啟新視窗而已。

步驟 2:按鈕點擊時開啟新視窗

修改 MainPage.xaml.cs 中的 Button1_Clicked 事件處理常式,


執行看看,每當滑鼠點一次 Button1,應該就會開啟一個新視窗。連續點三次 Button1 就會開啟三個新視窗。

從剛才的程式碼可知,開啟新視窗的方法是透過 Application 物件的 Current 屬性來取得目前執行中的應用程式,然後呼叫應用程式物件的 OpenWindow 方法來開啟新視窗。視窗物件是由 new Window() 來建立,並且在創建視窗物件時傳入 Page 物件。

👉 如果要關閉視窗,則是呼叫應用程式物件的 CloseWindow 方法

稍後的「練習四」有個影片,可以看到應用程式執行起來的樣子。值得一提的是,這種「多視窗」的設計雖然也可以運行於行動裝置,但是不同平台會有不同的呈現方式。

練習四:用 Visual Live Tree 觀察視窗內部結構

直接看影片比較快:


結語

雖然我正在寫的這個小工具只會執行於 Windows 平台,使用 MAUI 感覺有點 overkill 了,但是基於學習的目的,我還是會繼續添加一些功能,看看最終能走到哪裡、以及會碰到哪些問題。


先這樣吧。Keep coding!

延伸閱讀


沒有留言:

技術提供:Blogger.
回頂端⬆️