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


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

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

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

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

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

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

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

Примечание.

Это практическое руководство основано на публикации бложной серии Windows о WinUI, Создание приложения с единственным экземпляром (часть 3). Код для этих статей доступен на GitHub.

Отключение автоматического создания кода программы

Перед созданием окон необходимо проверить наличие перенаправления как можно раньше. Чтобы сделать это, мы должны определить символ "DISABLE_XAML_GENERATED_MAIN" в файле проекта. Выполните следующие действия, чтобы отключить автоматически созданный код программы:

  1. Щелкните правой кнопкой мыши имя project в Solution Explorer и выберите Edit Project File.

  2. Определите символ DISABLE_XAML_GENERATED_MAIN. Добавьте следующий XML-код в файл project:

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

Добавление символа DISABLE_XAML_GENERATED_MAIN отключает автоматически созданный код программы для project.

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

Настраиваемый Program.cs файл должен быть создан вместо выполнения метода Main по умолчанию. Код, добавленный в класс Program , позволяет приложению проверять перенаправление, которое не является поведением приложений WinUI по умолчанию.

  1. Перейдите в Solution Explorer, щелкните правой кнопкой мыши на имени проекта и выберите Добавить | Класс.

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

    DecideRedirection определяет, зарегистрировано ли приложение, путем регистрации уникального ключа, который представляет собой экземпляр вашего приложения. Основываясь на результатах регистрации ключей, он может определить, есть ли текущий экземпляр приложения, запущенного. После определения метод знает, следует ли перенаправлять или разрешать приложению продолжить запуск нового экземпляра. Метод 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. Однако одновременно можно запустить только один отладчик. Это ограничение запрещает нам знать, является ли приложение одним экземпляром, так как мы не можем выполнять отладку одного и того же project дважды одновременно. Для точного теста мы развернем приложение на локальном клиенте Windows. После развертывания можно запустить приложение на рабочем столе, как и любое приложение, установленное в Windows.

  1. Перейдите к Solution Explorer, щелкните правой кнопкой мыши имя project и выберите Deploy.

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

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

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

    Примечание.

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

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

    Совет

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

Итоги

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

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

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

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