Поделиться через


Создание одноэлементного приложения WinUI с помощью C#

В этом руководстве показано, как создать одно экземплярное приложение WinUI 3 с помощью C# и пакета SDK для приложений Windows. Одно экземпляры приложений разрешают одновременно только один экземпляр приложения. По умолчанию приложения WinUI являются несколькими экземплярами. Они позволяют одновременно запускать несколько экземпляров одного приложения. Это называется несколькими экземплярами. Однако вы можете реализовать одноуровневую настройку в зависимости от варианта использования приложения. Попытка запустить второй экземпляр одно экземпляра приложения приведет только к активации главного окна первого экземпляра. В этом руководстве показано, как реализовать одноуровневую настройку в приложении WinUI.

В этой статье раскрываются следующие темы:

  • Отключение созданного Program кода XAML
  • Определение настраиваемого Main метода перенаправления
  • Тестирование одноуровневой проверки после развертывания приложения

Предварительные требования

В этом руководстве используется Visual Studio и используется пустой шаблон приложения WinUI. Если вы не знакомы с разработкой WinUI, можно настроить, следуя инструкциям в статье "Начало работы с WinUI". Там вы установите Visual Studio, настройте его для разработки приложений с помощью WinUI, гарантируя наличие последней версии WinUI и пакета SDK для приложений Windows и создайте проект Hello World.

Когда вы сделали это, вернитесь сюда, чтобы узнать, как превратить проект Hello World в одно экземплярное приложение.

Примечание.

Это практическое руководство основано на публикации блога приложения с одним экземпляром (часть 3) из серии блогов Windows на WinUI 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 отключает автоматически созданный код программы для проекта.

Определение класса Program с помощью метода Main

Настраиваемый 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. Определите метод DecideRedirection под методом Main:

    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;
    }
    

    РешениеRedirection определяет, зарегистрировано ли приложение путем регистрации уникального ключа, представляющего экземпляр приложения. Основываясь на результатах регистрации ключей, он может определить, есть ли текущий экземпляр приложения, запущенного. После определения метод знает, следует ли перенаправлять или разрешать приложению продолжить запуск нового экземпляра. Метод RedirectActivationTo вызывается при необходимости перенаправления.

  6. Затем создадим метод RedirectActivationTo под методом DecideRedirection, а также требуемые инструкции 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. Наконец, определите вспомогательный метод OnActivated под методом DecideRedirection :

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

Тестирование одноуровневой проверки с помощью развертывания приложений

До этого момента мы тестируем приложение путем отладки в Visual Studio. Однако одновременно можно запустить только один отладчик. Это ограничение запрещает нам знать, является ли приложение одним экземпляром, так как мы не можем отлаживать один и тот же проект дважды одновременно. Для точного теста мы развернем приложение на локальном клиенте Windows. После развертывания можно запустить приложение на рабочем столе, как и любое приложение, установленное в Windows.

  1. Перейдите к Обозреватель решений, щелкните правой кнопкой мыши имя проекта и выберите "Развернуть".

  2. Откройте меню и щелкните поле поиска.

  3. Введите имя приложения в поле поиска.

  4. Щелкните значок приложения из результата поиска, чтобы запустить приложение.

    Примечание.

    Если приложение завершается сбоем в режиме выпуска, в пакете SDK для приложений Windows возникают некоторые известные проблемы. Вы можете отключить обрезку в проекте, установив для свойства PublishTrimmed значение false для всех конфигураций сборки в файлах проекта.pubxml. Дополнительные сведения см . в этой проблеме на сайте GitHub.

  5. Повторите шаги 2–4, чтобы снова запустить то же приложение и узнать, открывается ли другой экземпляр. Если приложение одноэлементно, первый экземпляр будет активирован вместо открытия нового экземпляра.

    Совет

    При необходимости можно добавить некоторый код ведения журнала в метод OnActivated , чтобы убедиться, что существующий экземпляр активирован. Попробуйте попросить Copilot помочь при добавлении реализации ILogger в приложение WinUI.

Итоги

Весь код, описанный здесь, находится на сайте GitHub с ветвями для различных шагов в исходной серии блогов Windows. См. ветвь с одним инстантированием для кода, относяющегося к этому инструкции. Ветвь main является самой полной. Другие ветви предназначены для отображения развития архитектуры приложения.

Развертывание приложений с помощью API жизненного цикла приложения

Создание одного экземпляра приложения (часть 3)

Пример WinAppSDK-DrumPad на GitHub