這份教學示範如何用 C# 和 Windows App SDK 建立單實例的 WinUI 應用程式。 單一實例應用程式一次只允許一個應用程式實例執行。 WinUI 應用程式預設為多重實例。 它們可讓您同時啟動相同應用程式的多個實例。 這是指多個實例。 不過,您可能會根據應用程式的使用案例來實作單一實例化。 嘗試啟動單一實例應用程式的第二個實例將只會激活第一個實例的主視窗。 本教學示範如何在 WinUI 應用程式中實作單一執行個體。
在本文中,您將了解如何:
- 關閉 XAML 生成的程式碼
Program - 定義重新導向的自定義
Main方法 - 在應用程式部署後測試單一實例運行
必要條件
這個教學使用 Visual Studio,並建立在 WinUI 空白應用程式範本之上。 如果你是 WinUI 開發新手,你可以依照 Get started with WinUI 的指示來設定。 在那裡你會安裝Visual Studio,設定它以使用 WinUI 開發應用程式,同時確保你擁有最新版本的 WinUI 和 Windows App SDK,然後建立一個Hello World project。
完成後,回來學習如何將你的「Hello World」project 轉化成單實例應用程式。
備註
這份教學是基於 Windows 部落格系列中關於 WinUI 的「 讓應用程式成為單實例(第三部分) 」部落格文章。 這些文章的程式碼可在 GitHub 取得。
停用自動產生的程序代碼
在建立任何視窗之前,我們需要儘早檢查是否有重新導向。 為此,我們必須在專案檔案中定義符號「DISABLE_XAML_GENERATED_MAIN」。 請遵循下列步驟來停用自動產生的程式代碼:
在Solution Explorer中右鍵點擊project名稱,選擇編輯Project檔案。
定義 DISABLE_XAML_GENERATED_MAIN 符號。 請將以下 XML 加入 project 檔案:
<PropertyGroup> <DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants> </PropertyGroup>
新增DISABLE_XAML_GENERATED_MAIN符號會停用你project自動產生的程式程式碼。
定義一個具有 Main 方法的 Program 類別
必須建立自定義Program.cs檔案,而不是執行預設的Main方法。 新增至 Program 類別的程式代碼可讓應用程式檢查重新導向,這不是 WinUI 應用程式的預設行為。
進入Solution Explorer,右鍵點擊project名稱,選擇 Add |Class。
將新類別
Program.cs命名為 ,然後選取 [ 新增]。將下列命名空間新增至 Program 類別,並取代任何現有的命名空間:
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.Windows.AppLifecycle;以下列內容取代空的 Program 類別:
public class Program { [STAThread] static int Main(string[] args) { WinRT.ComWrappersSupport.InitializeComWrappers(); bool isRedirect = DecideRedirection(); if (!isRedirect) { Application.Start((p) => { var context = new DispatcherQueueSynchronizationContext( DispatcherQueue.GetForCurrentThread()); SynchronizationContext.SetSynchronizationContext(context); _ = new App(); }); } return 0; } }Main 方法會決定應用程式應該重新導向至第一個實例,或在呼叫 DecideRedirection 之後啟動新的實例,我們將定義下一個實例。
在Main 方法下方定義 DecideRedirection 方法:
private static bool DecideRedirection() { bool isRedirect = false; AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs(); ExtendedActivationKind kind = args.Kind; AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp"); if (keyInstance.IsCurrent) { keyInstance.Activated += OnActivated; } else { isRedirect = true; RedirectActivationTo(args, keyInstance); } return isRedirect; }DecideRedirection 會藉由註冊代表應用程式實例的唯一索引鍵來判斷應用程式是否已註冊。 根據金鑰註冊的結果,它可以判斷是否有執行中應用程式的目前實例。 判斷之後,方法會知道是要重新導向還是允許應用程式繼續啟動新的實例。 如果需要重新導向,則會 呼叫 RedirectActivationTo 方法。
接下來,讓我們在 DecideRedirection 方法下方建立 RedirectActivationTo 方法,以及必要的 DllImport 語句。 將下列程式代碼新增至 Program 類別:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr CreateEvent( IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName); [DllImport("kernel32.dll")] private static extern bool SetEvent(IntPtr hEvent); [DllImport("ole32.dll")] private static extern uint CoWaitForMultipleObjects( uint dwFlags, uint dwMilliseconds, ulong nHandles, IntPtr[] pHandles, out uint dwIndex); [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd); private static IntPtr redirectEventHandle = IntPtr.Zero; // Do the redirection on another thread, and use a non-blocking // wait method to wait for the redirection to complete. public static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance) { redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null); Task.Run(() => { keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); SetEvent(redirectEventHandle); }); uint CWMO_DEFAULT = 0; uint INFINITE = 0xFFFFFFFF; _ = CoWaitForMultipleObjects( CWMO_DEFAULT, INFINITE, 1, [redirectEventHandle], out uint handleIndex); // Bring the window to the foreground Process process = Process.GetProcessById((int)keyInstance.ProcessId); SetForegroundWindow(process.MainWindowHandle); }RedirectActivationTo 方法負責將啟用重新導向至應用程式的第一個實例。 它會建立事件句柄、啟動新的線程來重新導向啟用,並等候重新導向完成。 重新導向完成之後,方法會將視窗帶到最前面。
最後,在 DecideRedirection 方法下方定義 Helper 方法 OnActivated:
private static void OnActivated(object sender, AppActivationArguments args) { ExtendedActivationKind kind = args.Kind; }
透過應用程式部署測試單一實例
到目前為止,我們都是透過在 Visual Studio 中除錯來測試這個應用程式。 不過,我們一次只能執行一個調試程式。 這個限制讓我們無法確認應用程式是否為單一實例模式,因為我們無法同時除錯同一個專案。 為了進行精確的測試,我們會將應用程式部署至本機 Windows 用戶端。 部署之後,我們可以像在 Windows 上安裝任何應用程式一樣,從桌面啟動應用程式。
進入Solution Explorer,右鍵點擊project名稱,選擇Deploy。
開啟 [開始] 功能表,然後按下搜尋欄位。
在搜尋欄位中輸入應用程式的名稱。
按下搜尋結果中的應用程式圖示以啟動您的應用程式。
備註
如果你在發佈模式下遇到應用程式當機,代表 Windows App SDK 中被裁剪的應用程式有一些已知的問題。 你可以在專案中將 PublishTrimmed 屬性設定為 false,來關閉在專案
.pubxml檔案中的所有建置組態中修整功能。 欲了解更多資訊,請參閱GitHub本期。重複步驟 2 到 4,再次啟動相同的應用程式,並查看另一個實例是否開啟。 如果應用程式是單一實例,則會啟動第一個實例,而不是開啟新的實例。
提示
您可以選擇性地將一些記錄程式代碼新增至 OnActivated 方法,以確認現有的實例已啟動。 請嘗試詢問 Copilot 以協助新增 ILogger 實作到您的 WinUI 應用程式中。
摘要
這裡涵蓋的所有程式碼都在 GitHub,並有原始 Windows 部落格系列 中不同步驟的分支。 請參閱 single-instancing 分支,了解此操作指南的具體程式碼。 分支 main 是最全面的。 其他分支旨在說明應用程式架構如何演進。