Compartilhar via


Criar um aplicativo .NET em C# com a interoperabilidade WinUI 3 e Win32

Neste tópico, explicamos como criar um aplicativo .NET em C# básico com recursos de interoperabilidade WinUI 3 e Win32 usando pInvoke (Serviços de Invocação de Plataforma).

Prerequisites

  1. começar a desenvolver aplicativos do Windows

Aplicativo C#/.NET gerenciado básico

Para este exemplo, especificaremos o local e o tamanho da janela do aplicativo, converteremos e dimensionaremos para o DPI apropriado, desabilitaremos os botões de minimizar e maximizar a janela e, por fim, consultaremos o processo atual para mostrar uma lista dos módulos carregados no processo atual.

Vamos criar nosso aplicativo de exemplo a partir do aplicativo de modelo inicial (consulte Pré-requisitos). Confira também Modelos da WinUI 3 no Visual Studio.

O arquivo MainWindow.xaml

Com o WinUI 3, você pode criar instâncias da classe Window na marcação XAML.

A classe janela XAML foi estendida para dar suporte a janelas da área de trabalho, transformando-a em uma abstração de cada uma das implementações de janela de baixo nível usadas pelos modelos de aplicativo da UWP e da área de trabalho. Especificamente, CoreWindow para UWP e identificadores de janela (ou HWNDs) para Win32.

O código a seguir mostra o arquivo MainWindow.xaml do aplicativo de modelo inicial, que usa a classe Window como o elemento raiz do aplicativo.

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

Configuration

  1. Para chamar APIs Win32 exportadas de User32.dll, você pode usar o C#/Win32 P/Invoke Source Generator em seu projeto do Visual Studio. Clique em Ferramentas>Gerenciador de Pacotes NuGet>Gerenciar Pacotes NuGet para Solução..., e (na guia Procurar) pesquise Microsoft.Windows.CsWin32. Para obter mais detalhes, confira Como chamar funções nativas por meio de código gerenciado.

    Opcionalmente, você pode confirmar que a instalação foi bem-sucedida confirmando que Microsoft.Windows.CsWin32 está listado no nóPacotes de > no Gerenciador de Soluções.

    Opcionalmente, você também pode clicar duas vezes no arquivo de projeto do aplicativo (ou clicar com o botão direito do mouse e selecionar Editar arquivo de projeto) para abrir o arquivo em um editor de texto e confirmar se o arquivo de projeto agora inclui um PackageReference NuGet para "Microsoft.Windows.CsWin32".

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.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>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
        <PublishProfile>win-$(Platform).pubxml</PublishProfile>
        <UseWinUI>true</UseWinUI>
        <EnableMsixTooling>true</EnableMsixTooling>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
      <ItemGroup>
        <Content Include="Assets\SplashScreen.scale-200.png" />
        <Content Include="Assets\LockScreenLogo.scale-200.png" />
        <Content Include="Assets\Square150x150Logo.scale-200.png" />
        <Content Include="Assets\Square44x44Logo.scale-200.png" />
        <Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
        <Content Include="Assets\StoreLogo.png" />
        <Content Include="Assets\Wide310x150Logo.scale-200.png" />
      </ItemGroup>
    
      <ItemGroup>
        <Manifest Include="$(ApplicationManifest)" />
      </ItemGroup>
    
      <!--
        Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
        Tools extension to be activated for this project even if the Windows App SDK Nuget
        package has not yet been restored.
      -->
      <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
        <ProjectCapability Include="Msix" />
      </ItemGroup>
      <ItemGroup>
        <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
          <PrivateAssets>all</PrivateAssets>
          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
        <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250205002" />
      </ItemGroup>
    
      <!--
        Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
        Explorer "Package and Publish" context menu entry to be enabled for this project even if
        the Windows App SDK Nuget package has not yet been restored.
      -->
      <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
        <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
      </PropertyGroup>
    
      <!-- Publish Properties -->
      <PropertyGroup>
        <PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
        <PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
        <PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
        <PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
      </PropertyGroup>
    </Project>
    
  2. Adicione um arquivo de texto ao seu projeto e nomeie-o NativeMethods.txt. O conteúdo deste arquivo informa ao Gerador de Origem C#/Win32 P/Invoke as funções e os tipos para os quais você deseja gerar código-fonte P/Invoke. Em outras palavras, quais funções e tipos você chamará e usará em seu código C#.

    GetDpiForWindow
    GetWindowLong
    SetWindowPos
    SetWindowLong
    HWND_TOP
    WINDOW_STYLE
    

Code

  1. App.xaml.cs No arquivo code-behind, obtemos um identificador para a janela usando o método de interoperabilidade WindowsNative.GetWindowHandle WinRT COM (consulte Recuperar um identificador de janela (HWND)).

    Esse método é chamado do manipulador OnLaunched do aplicativo, conforme mostrado aqui:

    /// <summary>
    /// Invoked when the application is launched.
    /// </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. Em seguida, chamamos um método SetWindowDetails, passando o identificador e as dimensões preferenciais da janela.

    Neste método:

    • Chamamos GetDpiForWindow para obter o valor de pontos por polegada (dpi) para a janela (o Win32 usa pixels físicos, enquanto o WinUI 3 usa pixels efetivos). Esse valor de dpi é usado para calcular o fator de escala e aplicá-lo à largura e à altura especificadas para a janela.
    • Em seguida, chamamos SetWindowPos para especificar o local desejado da janela.
    • Por fim, chamamos SetWindowLong para desabilitar os botões Minimizar e Maximizar .
    private static void SetWindowDetails(IntPtr hwnd, int width, int height)
    {
        var dpi = Windows.Win32.PInvoke.GetDpiForWindow((Windows.Win32.Foundation.HWND)hwnd);
        float scalingFactor = (float)dpi / 96;
        width = (int)(width * scalingFactor);
        height = (int)(height * scalingFactor);
    
        _ = Windows.Win32.PInvoke.SetWindowPos((Windows.Win32.Foundation.HWND)hwnd,
                                    Windows.Win32.Foundation.HWND.HWND_TOP,
                                    0, 0, width, height,
                                    Windows.Win32.UI.WindowsAndMessaging.SET_WINDOW_POS_FLAGS.SWP_NOMOVE);
    
        var nIndex = Windows.Win32.PInvoke.GetWindowLong((Windows.Win32.Foundation.HWND)hwnd,
                  Windows.Win32.UI.WindowsAndMessaging.WINDOW_LONG_PTR_INDEX.GWL_STYLE) &
                  ~(int)Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE.WS_MINIMIZEBOX &
                  ~(int)Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE.WS_MAXIMIZEBOX;
    
        _ = Windows.Win32.PInvoke.SetWindowLong((Windows.Win32.Foundation.HWND)hwnd,
               Windows.Win32.UI.WindowsAndMessaging.WINDOW_LONG_PTR_INDEX.GWL_STYLE,
               nIndex);
    }
    
  3. No arquivo MainWindow.xaml, usamos um ContentDialog com um ScrollViewer para exibir uma lista de todos os módulos carregados para o processo atual.

    <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. Em seguida, substituímos o manipulador de eventos MyButton_Click pelo código a seguir.

    Aqui, obtemos uma referência ao processo atual chamando GetCurrentProcess. Em seguida, iteramos pela coleção de Módulos e acrescentamos o nome do arquivo de cada ProcessModule à nossa cadeia de caracteres de exibição.

    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. Compile e execute o aplicativo.

  6. Depois que a janela for exibida, selecione o botão "Exibir módulos carregados".

    Captura de tela do aplicativo de interoperabilidade Win32 básico descrito neste tópico.
    o aplicativo de interoperabilidade básico do Win32 descrito neste tópico.

Summary

Neste tópico, abordamos o acesso à implementação da janela subjacente (neste caso, Win32 e HWNDs) e o uso de APIs do Win32 junto com as APIs do WinRT. Isso demonstra como você pode usar o código do aplicativo da área de trabalho existente ao criar novos aplicativos da área de trabalho do WinUI 3.

Para obter um exemplo mais abrangente, consulte o exemplo da galeria AppWindow no repositório Exemplos do SDK de Aplicativos do Windows GitHub.

Um exemplo para personalizar a barra de título da janela

Neste segundo exemplo, mostramos como personalizar a barra de título da janela e seu conteúdo. Antes de seguir, examine estes tópicos:

Criar um novo projeto

  1. No Visual Studio, crie um projeto C# ou C++/WinRT do modelo de projeto Aplicativo em branco, empacotado (WinUI 3 na Área de Trabalho).

Configuration

  1. Novamente, faça referência ao pacote NuGet Microsoft.Windows.CsWin32 , assim como fizemos no primeiro exemplo.

  2. Adicione um arquivo de texto NativeMethods.txt ao seu projeto.

    LoadImage
    SendMessage
    SetWindowText
    WM_SETICON
    

MainWindow.xaml

Note

Se você precisar de um arquivo de ícone para usar com este passo a passo, poderá baixar o arquivo computer.ico do aplicativo de exemplo WirelessHostednetwork. Coloque esse arquivo na pasta Assets e adicione o arquivo ao seu projeto como conteúdo. Em seguida, você poderá consultar o arquivo usando a url Assets/computer.ico.

Caso contrário, fique à vontade para usar um arquivo de ícone que você já tem e altere as duas referências a ele nas listagens de código abaixo.

  1. Na listagem de código abaixo, você verá que, em MainWindow.xaml nós, adicionamos dois botões e especificamos manipuladores de clique para cada um. No manipulador Clique para o primeiro botão (basicButton_Click), definimos o ícone e o texto da barra de título. No segundo (customButton_Click), demonstramos uma personalização mais significativa substituindo a barra de título pelo conteúdo do StackPanel chamado customTitleBarPanel.
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="window_titlebar.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:window_titlebar"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="Basic WinUI 3 Window title bar sample">

    <Grid x:Name="rootElement" RowDefinitions="100, *, 100, *">

        <StackPanel x:Name="customTitleBarPanel" Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Top" Visibility="Collapsed">
            <Image Source="Images/windowIcon.gif" />
            <TextBlock VerticalAlignment="Center" Text="Full customization of title bar"/>
        </StackPanel>

        <StackPanel x:Name="buttonPanel"  Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button x:Name="basicButton" Click="basicButton_Click" Margin="25">Set the Window title and icon</Button>
            <Button x:Name="customButton" Click="customButton_Click" Margin="25">Customize the window title bar</Button>
        </StackPanel>

    </Grid>
</Window>

MainWindow.xaml.cs/cpp

  1. Na listagem de código abaixo para o manipulador basicButton_Click, para manter a barra de título personalizada oculta, recolhemos o customTitleBarPanelStackPanel e definimos a propriedade ExtendsContentIntoTitleBar como false.
  2. Em seguida, chamamos IWindowNative::get_WindowHandle (para C#, usando o método auxiliar de interoperabilidade GetWindowHandle) para recuperar o HWND (identificador de janela) da janela principal.
  3. Em seguida, definimos o ícone do aplicativo (para C#, usando o pacote NuGet PInvoke.User32 ) chamando as funções LoadImage e SendMessage .
  4. Por fim, chamamos SetWindowText para atualizar a cadeia de caracteres da barra de título.
private void basicButton_Click(object sender, RoutedEventArgs e)
{
    // Ensure the custom title bar content is not displayed.
    customTitleBarPanel.Visibility = Visibility.Collapsed;

    // Disable custom title bar content.
    ExtendsContentIntoTitleBar = false;

    //Get the Window's HWND
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);

    var hIcon = Windows.Win32.PInvoke.LoadImage(
        null,
        "Images/windowIcon.ico",
        Windows.Win32.UI.WindowsAndMessaging.GDI_IMAGE_TYPE.IMAGE_ICON,
        20, 20,
        Windows.Win32.UI.WindowsAndMessaging.IMAGE_FLAGS.LR_LOADFROMFILE);

    Windows.Win32.PInvoke.SendMessage(
        (Windows.Win32.Foundation.HWND)hwnd,
        Windows.Win32.PInvoke.WM_SETICON,
        (Windows.Win32.Foundation.WPARAM)0,
        (Windows.Win32.Foundation.LPARAM)hIcon.DangerousGetHandle());

    Windows.Win32.PInvoke.SetWindowText((Windows.Win32.Foundation.HWND)hwnd, "Basic customization of title bar");
}
// pch.h
...
#include <microsoft.ui.xaml.window.h>
...

// MainWindow.xaml.h
...
void basicButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
...

// MainWindow.xaml.cpp
void MainWindow::basicButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    // Ensure the that custom title bar content is not displayed.
    customTitleBarPanel().Visibility(Visibility::Collapsed);

    // Disable custom title bar content.
    ExtendsContentIntoTitleBar(false);

    // Get the window's HWND
    auto windowNative{ this->m_inner.as<::IWindowNative>() };
    HWND hWnd{ 0 };
    windowNative->get_WindowHandle(&hWnd);

    HICON icon{ reinterpret_cast<HICON>(::LoadImage(nullptr, L"Assets/computer.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE)) };
    ::SendMessage(hWnd, WM_SETICON, 0, (LPARAM)icon);

    this->Title(L"Basic customization of title bar");
}
  1. No manipulador customButton_Click , definimos a visibilidade do customTitleBarPanelStackPanel como Visible.
  2. Em seguida, definimos a propriedade ExtendsContentIntoTitleBar para true, e chamamos SetTitleBar para exibir customTitleBarPanelStackPanel como a barra de título personalizada.
private void customButton_Click(object sender, RoutedEventArgs e)
{
    customTitleBarPanel.Visibility = Visibility.Visible;

    // Enable custom title bar content.
    ExtendsContentIntoTitleBar = true;
    // Set the content of the custom title bar.
    SetTitleBar(customTitleBarPanel);
}
// MainWindow.xaml.h
...
void customButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
...

// MainWindow.xaml.cpp
void MainWindow::customButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    customTitleBarPanel().Visibility(Visibility::Visible);

    // Enable custom title bar content.
    ExtendsContentIntoTitleBar(true);

    // Set the content of the custom title bar.
    SetTitleBar(customTitleBarPanel());
}

App.xaml

  1. No arquivo App.xaml, imediatamente após o comentário <!-- Other app resources here -->, adicionamos alguns pincéis de cor personalizada para a barra de título, conforme mostrado abaixo.
<?xml version="1.0" encoding="utf-8"?>
<Application
    x:Class="window_titlebar.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:window_titlebar">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
                <!-- Other merged dictionaries here -->
            </ResourceDictionary.MergedDictionaries>
            <!-- Other app resources here -->
            <SolidColorBrush x:Key="WindowCaptionBackground">Green</SolidColorBrush>
            <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">LightGreen</SolidColorBrush>
            <SolidColorBrush x:Key="WindowCaptionForeground">Red</SolidColorBrush>
            <SolidColorBrush x:Key="WindowCaptionForegroundDisabled">Pink</SolidColorBrush>
        </ResourceDictionary>
    </Application.Resources>
</Application>
  1. Se você estiver acompanhando essas etapas em seu próprio aplicativo, poderá criar seu projeto agora e executar o aplicativo. Você verá uma janela do aplicativo semelhante à seguinte (com o ícone de aplicativo personalizado):

    aplicativo modelo sem personalização.
    Aplicativo de template.

  • Aqui está a barra de título personalizada básica:

    Aplicativo de modelo com ícone de aplicativo personalizado.
    Aplicativo de modelo com ícone de aplicativo personalizado.

  • Aqui está a barra de título totalmente personalizada:

    Aplicativo de modelo com barra de título personalizada.
    App modelo com barra de título personalizada.

Consulte também