基於學習的目的,我開始用 MAUI 來寫一些執行於 Windows 平台的小工具。這篇筆記會包含兩個入門的小練習,並解說 MAUI 專案的檔案結構。
- 開發環境
- 第一支 MAUI 程式
- 了解 MAUI 專案結構
- MainPage
- 練習一:增加一個按鈕並撰寫事件處理常式
- 練習二:修改視窗大小與位置(限定 Windows 平台)
- 練習三:開啟新視窗(跨平台,但通常只用於 Windows)
- 練習四:用 Visual Live Tree 觀察視窗內部結構
- 結語
開發環境
預設情況下,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 視窗裡面查看專案中有哪些程式檔案,然後在應用程式執行的狀態下,按以下步驟修改程式碼:
- 開啟 MainPage.xaml。
- 找到 Label 元素,把 Text 屬性值從原本的 "Hello, World!" 改為 "我的第一支 MAUI 程式!"。
- 切換至執行中的應用程式,此時應該會看到視窗中已經出現剛才輸入的文字。此步驟可觀察 Visual Studio 的 Hot Reload 功能是否正常運作。
- 把自製的 .NET 吉祥物圖檔以滑鼠拖曳至 Solution Explorer 的 Resources\Images 資料夾(也可以用加入檔案的方式),並查看 Properties 視窗以確認該檔案的 Build Action 是「MauiImage」。
- 回到 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,相關功能已經實作完成且合併至主分支。我參考其中的說明,試出以下程式碼可以達到我想要的效果,也就是在主視窗開啟的時候自動設定視窗大小,並顯示於螢幕的正中央。
public partial class App : Application | |
{ | |
public App() | |
{ | |
InitializeComponent(); | |
MainPage = new AppShell(); | |
} | |
protected override Window CreateWindow(IActivationState activationState) | |
{ | |
Window window = base.CreateWindow(activationState); | |
window.Activated += Window_Activated; | |
return window; | |
} | |
private async void Window_Activated(object sender, EventArgs e) | |
{ | |
#if WINDOWS | |
const int DefaultWidth = 1024; | |
const int DefaultHeight = 800; | |
var window = sender as Window; | |
// 變更視窗大小 | |
window.Width = DefaultWidth; | |
window.Height = DefaultHeight; | |
// 給它一點時間來把「變更視窗大小」的操作確實完成。 | |
await window.Dispatcher.DispatchAsync(() => { }); | |
// 取得主要顯示器的資訊 | |
var disp = DeviceDisplay.Current.MainDisplayInfo; | |
// 將視窗置於螢幕中央 | |
window.X = (disp.Width / disp.Density - window.Width) / 2; | |
window.Y = (disp.Height / disp.Density - window.Height) / 2; | |
#endif | |
} | |
} |
雖然程式碼看似比先前那個使用 Win32 API 的寫法還要長,但那是因為這次還加入了「把視窗移動至螢幕中央」的處理。我選擇在視窗獲得輸入焦點的時候(Activated 事件)來改變視窗大小和位置。視窗物件還有哪些事件,也可順便了解一下:.NET MAUI 應用程式週期。
練習三:開啟新視窗
步驟 1:加入一個新頁
步驟 2:按鈕點擊時開啟新視窗
private void Button1_Clicked(object sender, EventArgs e) | |
{ | |
var win = new Window | |
{ | |
Page = new NewPage1(), | |
Width = 1024, | |
Height = 300 // 亦可在此設定初始座標 X 和 Y | |
}; | |
Application.Current.OpenWindow(win); | |
} |
執行看看,每當滑鼠點一次 Button1,應該就會開啟一個新視窗。連續點三次 Button1 就會開啟三個新視窗。
練習四:用 Visual Live Tree 觀察視窗內部結構
結語
雖然我正在寫的這個小工具只會執行於 Windows 平台,使用 MAUI 感覺有點 overkill 了,但是基於學習的目的,我還是會繼續添加一些功能,看看最終能走到哪裡、以及會碰到哪些問題。
先這樣吧。Keep coding!
延伸閱讀
- .NET MAUI 簡介:從 .NET 歷史說起
- .NET MAUI Tutorial for Beginners - Build iOS, Android, macOS, & Windows Apps with C# & Visual Studio by James Montemagno(長達一小時的教學影片)
- 微軟文件:使用語意屬性建置無障礙應用程式
- 微軟文件:.NET MAUI 應用程式週期
沒有留言: