Создание приложения C# .NET с использованием WinUI 3 и возможностей взаимодействия Win32

В этой статье показано, как создать базовое приложение C# .NET с возможностями взаимодействия WinUI 3 и Win32 с помощью служб вызова платформы (PInvoke).

Необходимые компоненты

  1. Настройте среду разработки, как описано в статье Установка инструментов для Windows App SDK.
  2. Проверьте конфигурацию, выполнив действия, описанные в статье Создание первого проекта WinUI 3.

Базовое управляемое приложение C#/.NET

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

Мы создадим пример приложения на основе исходного шаблона приложения (см. раздел Предварительные требования). Также см. статью Шаблоны WinUI 3 в Visual Studio.

Файл MainWindow.xaml

С помощью WinUI 3 можно создавать экземпляры класса Window в разметке XAML.

Класс Window XAML был расширен для поддержки окон рабочего стола, преобразуя его в абстракцию всех низкоуровневых реализаций окон, которые используются моделями UWP и классическими приложениями. В частности, CoreWindow для UWP и дескрипторы окон (или объекты HWND) для Win32.

В следующем примере кода показан файл MainWindow.xaml из исходного приложения-шаблона, в котором класс Window используется в качестве корневого элемента для приложения.

<Window
    x:Class="WinUI_3_basic_win32_interop.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinUI_3_basic_win32_interop"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    </StackPanel>
</Window>

Настройка

  1. Для вызова API-интерфейсов Win32, предоставленных в библиотеке User32.dll, добавьте PInvoke.User32 пакет NuGet с открытым кодом в проект Visual Studio (в меню Visual Studio выберите Инструменты -> Диспетчер пакетов NuGet -> Управление пакетами NuGet для решения... и найдите "Pinvoke.User32"). Дополнительные сведения см. в статье Вызов собственных функций из управляемого кода.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    Диспетчер пакетов NuGet с выбранным PInvoke.User32.

    Убедитесь, что установка прошла успешно, проверив папку Packages в проекте VS.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Пакеты Обозревателя решений с PInvoke.User32.

    Затем дважды щелкните файл проекта приложения (или щелкните правой кнопкой мыши и выберите "Изменить файл проекта"), чтобы открыть файл в текстовом редакторе и убедиться, что файл проекта теперь содержит NuGet PackageReference для PInvoke.User32.

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
        <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
        <RootNamespace>WinUI_3_basic_win32_interop</RootNamespace>
        <ApplicationManifest>app.manifest</ApplicationManifest>
        <Platforms>x86;x64;arm64</Platforms>
        <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
        <UseWinUI>true</UseWinUI>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.ProjectReunion" Version="0.8.1" />
        <PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.1" />
        <PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.1" />
        <PackageReference Include="PInvoke.User32" Version="0.7.104" />
        <Manifest Include="$(ApplicationManifest)" />
      </ItemGroup>
    </Project>
    

Код

  1. В файле кода программной части App.xaml.cs мы получаем дескриптор для Window с помощью метода COM-взаимодействия WinRT WindowNative.GetWindowHandle (см. статью Получение дескриптора окна (HWND)).

    Этот метод вызывается из обработчика приложения OnLaunched, как показано ниже:

    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used such as when the application is launched to open a specific file.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
    
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(m_window);
    
        SetWindowDetails(hwnd, 800, 600);
    
        m_window.Activate();
    }
    
  2. Затем вызовем метод SetWindowDetails, передав дескриптор окна и предпочтительные измерения. Не забудьте добавить директиву using static PInvoke.User32;.

    В этом методе мы выполняем следующие действия:

    • Вызываем GetDpiForWindow, чтобы получить значение точек на дюйм (DPI) для окна (в Win32 используются фактические пиксели, а в WinUI 3 — эффективные пиксели). Это значение точек на дюйм используется для вычисления коэффициента масштабирования и применения его к ширине и высоте, указанным для окна.
    • Затем вызываем SetWindowPos, чтобы указать нужное расположение окна.
    • Наконец, вызываем SetWindowLong для отключения кнопок Свернуть и Развернуть.
    private static void SetWindowDetails(IntPtr hwnd, int width, int height)
    {
        var dpi = GetDpiForWindow(hwnd);
        float scalingFactor = (float)dpi / 96;
        width = (int)(width * scalingFactor);
        height = (int)(height * scalingFactor);
    
        _ = SetWindowPos(hwnd, SpecialWindowHandles.HWND_TOP,
                                    0, 0, width, height,
                                    SetWindowPosFlags.SWP_NOMOVE);
        _ = SetWindowLong(hwnd, 
               WindowLongIndexFlags.GWL_STYLE,
               (SetWindowLongFlags)(GetWindowLong(hwnd,
                  WindowLongIndexFlags.GWL_STYLE) &
                  ~(int)SetWindowLongFlags.WS_MINIMIZEBOX &
                  ~(int)SetWindowLongFlags.WS_MAXIMIZEBOX));
    }
    
  3. В файле MainWindow.xaml мы используем ContentDialog с ScrollViewer для вывода списка всех модулей, загруженных для текущего процесса.

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Display loaded modules</Button>
    
        <ContentDialog x:Name="contentDialog" CloseButtonText="Close">
            <ScrollViewer>
                <TextBlock x:Name="cdTextBlock" TextWrapping="Wrap" />
            </ScrollViewer>
        </ContentDialog>
    
    </StackPanel>
    
  4. Затем замените обработчик событий MyButton_Click следующим кодом.

    Здесь мы получаем ссылку на текущий процесс, вызвав GetCurrentProcess. Затем выполним итерацию по коллекции модулей и добавим имя файла каждого объекта ProcessModule в отображаемую строку.

    private async void myButton_Click(object sender, RoutedEventArgs e)
    {
        myButton.Content = "Clicked";
    
        var description = new System.Text.StringBuilder();
        var process = System.Diagnostics.Process.GetCurrentProcess();
        foreach (System.Diagnostics.ProcessModule module in process.Modules)
        {
            description.AppendLine(module.FileName);
        }
    
        cdTextBlock.Text = description.ToString();
        await contentDialog.ShowAsync();
    }
    
  5. Скомпилируйте и запустите приложение.

  6. После появления окна нажмите кнопку Display loaded modules (Отобразить загруженные модули).

    Screenshot of the basic Win32 interop application described in this topic.
    Базовое приложение Win32 взаимодействия, описанное в этой статье.

Итоги

В этом статье мы рассмотрели доступ к базовой реализации окна, в данном случае Win32 и HWND, а также использование API-интерфейсов Win32 вместе с API-интерфейсами WinRT. Благодаря этому при создании классических приложений WinUI 3 мы можем использовать часть имеющегося кода классического приложения.

Более обширный пример см. в образце коллекции AppWindow в репозитории GitHub Примеры использования пакета SDK для приложений Windows.

См. также