共用方式為


使用 C 建立單一實例的 WinUI 應用程式#

本操作說明示範如何使用 C# 和 Windows 應用程式 SDK 建立單一實例 WinUI 3 應用程式。 單一實例應用程式一次只允許一個應用程式實例執行。 WinUI 應用程式預設為多重實例。 它們可讓您同時啟動相同應用程式的多個實例。 這是指多個實例。 不過,您可能想要根據應用程式的使用案例來實作單一實例。 嘗試啟動單一實例應用程式的第二個實例只會改為啟動第一個實例的主視窗。 本教學課程示範如何在 WinUI 應用程式中實作單一實例。

在本文中,您將了解如何:

  • 關閉 XAML 產生的程式 Program 代碼
  • 定義重新導向的自定義 Main 方法
  • 在應用程式部署後測試單一實例

必要條件

本教學課程使用 Visual Studio,並在 WinUI 空白應用程式範本上建置。 如果您不熟悉 WinUI 開發,您可以依照開始使用 WinUI 中的指示進行設定。 您將安裝 Visual Studio、設定它以使用 WinUI 開發應用程式,同時確保您擁有最新版本的 WinUI 和 Windows 應用程式 SDK,並建立 Hello World 專案。

當您完成此動作時,請回到這裡,瞭解如何將 「Hello World」 項目轉換成單一實例應用程式。

注意

本作法是以 WinUI 3 上 Windows 部落格系列中的製作應用程式單一實例 (第 3 部分) 部落格文章為基礎。 這些文章的程式代碼可在 GitHub取得。

停用自動產生的程序代碼

在建立任何視窗之前,我們需要儘早檢查重新導向。 若要這樣做,我們必須在專案檔中定義符號 「DISABLE_XAML_GENERATED_MAIN」。。 請遵循下列步驟來停用自動產生的程式代碼:

  1. 以滑鼠右鍵按兩下 方案總管中的專案名稱,然後選取 [編輯項目檔]。

  2. 定義 DISABLE_XAML_GENERATED_MAIN 符號。 將下列 XML 新增至專案檔:

    <PropertyGroup>
      <DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    

新增DISABLE_XAML_GENERATED_MAIN符號將會停用專案的自動產生程序代碼。

使用Main方法定義 Program 類別

必須建立自定義Program.cs檔案,而不是執行預設的Main方法。 新增至 Program 類別的程式代碼可讓應用程式檢查重新導向,這不是 WinUI 應用程式的預設行為。

  1. 流覽至 [方案總管],以滑鼠右鍵按兩下專案名稱,然後選取 [新增] |類別

  2. 將新類別 Program.cs 命名為 ,然後選取 [ 新增]。

  3. 將下列命名空間新增至 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;
    
  4. 以下列專案取代空 的 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 之後啟動新的實例,我們將定義下一個實例。

  5. 在 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 方法。

  6. 接下來,讓我們在 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 方法負責將啟用重新導向至應用程式的第一個實例。 它會建立事件句柄、啟動新的線程來重新導向啟用,並等候重新導向完成。 重新導向完成之後,方法會將視窗帶入前景。

  7. 最後,在 DecideRedirection 方法下方定義 Helper 方法 OnActivated

    private static void OnActivated(object sender, AppActivationArguments args)
    {
        ExtendedActivationKind kind = args.Kind;
    }
    

透過應用程式部署測試單一實例

在這一點之前,我們已在 Visual Studio 內偵錯來測試應用程式。 不過,我們一次只能執行一個調試程式。 這項限制可防止我們知道應用程式是否為單一實例,因為我們無法同時偵錯相同的專案兩次。 為了進行精確的測試,我們會將應用程式部署至本機 Windows 用戶端。 部署之後,我們可以像在 Windows 上安裝任何應用程式一樣,從桌面啟動應用程式。

  1. 流覽至 [方案總管],以滑鼠右鍵按下專案名稱,然後選取 [部署]。

  2. 開啟 [開始] 功能表,然後按下搜尋欄位。

  3. 在搜尋欄位中輸入應用程式的名稱。

  4. 按下搜尋結果中的應用程式圖示以啟動您的應用程式。

    注意

    如果您在發行模式中遇到應用程式當機,則 Windows 應用程式 SDK 中已修剪的應用程式有一些已知問題。 您可以將 Project 檔案中所有建置組態的 PublishTrimmed 屬性設定false,以停用專案中的.pubxml修剪。 如需詳細資訊,請參閱 GitHub 上的此問題

  5. 重複步驟 2 到 4,再次啟動相同的應用程式,並查看另一個實例是否開啟。 如果應用程式是單一實例,則會啟動第一個實例,而不是開啟新的實例。

    提示

    您可以選擇性地將一些記錄程式代碼新增至 OnActivated 方法,以確認現有的實例已啟動。 請嘗試詢問 Copilot 以協助將 ILogger 實作新增至您的 WinUI 應用程式。

摘要

這裡涵蓋的所有程式代碼都位於 GitHub,原始 Windows 部落格系列中不同步驟的分支。 如需此操作說明的特定程式代碼,請參閱單一實例分支。 分支 main 是最全面的。 其他分支旨在說明應用程式架構如何演進。

應用程式生命週期 API 的應用程式執行個體

將應用程式設定為單一實體 (第 3 部分)

GitHub 上的 WinAppSDK-DrumPad 範例