Créer une application C# .NET avec l’interopérabilité WinUI 3 et Win32

Dans cet article, nous allons vous guider dans la création d’une application C# .NET de base avec WinUI 3 et des fonctionnalités d’interopérabilité Win32 à l’aide de Platform Invocation Services (PInvoke).

Prerequisites

  1. Configurez votre environnement de développement comme décrit dans Installer des outils pour le SDK d’application Windows.
  2. Testez votre configuration en suivant les étapes décrites dans Créer votre premier projet WinUI 3.

Application C#/.NET managée de base

Pour cet exemple, nous allons spécifier l’emplacement et la taille de la fenêtre d’application, la convertir et la mettre à l’échelle pour la résolution appropriée, désactiver les boutons Réduire et Agrandir de la fenêtre, puis interroger le processus en cours pour afficher la liste des modules chargés dans ce dernier.

Nous allons créé notre application exemple à partir de l’application de modèle initiale (voir Prérequis). Voir également Modèles WinUI 3 dans Visual Studio.

Le fichier MainWindow.xaml

Avec WinUI 3, vous pouvez créer des instances de la classe Window dans le balisage XAML.

La classe XAML Window a été étendue pour prendre en charge les fenêtres de bureau, la transformant en une abstraction de chacune des implémentations de fenêtre de bas niveau utilisées par les modèles d’application UWP et de bureau. Plus précisément, CoreWindow pour UWP et les handles de fenêtre (ou HWND) pour Win32.

Le code suivant montre le fichier MainWindow.xaml de l’application modèle initiale, qui utilise la classe Window comme élément racine pour l’application.

<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. Pour appeler des API Win32 exposées dans User32.dll, ajoutez le package NuGet PInvoke.User32 au projet VS (à partir du menu Visual Studio, sélectionnez Outils -> Gestionnaire de packages NuGet -> Gérer les packages NuGet pour la solution... et recherchez « PInvoke.User32 »). Pour plus d’informations, consultez Appel de fonctions natives à partir de code managé.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    Gestionnaire de package NuGet avec PInvoke.User32 sélectionné.

    Vérifiez que l’installation a réussi en consultant le dossier Packages dans le projet VS.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Packages de l’Explorateur de solutions avec PInvoke.User32.

    Ensuite, double-cliquez sur le fichier projet de l’application (ou cliquez avec le bouton droit et sélectionnez « Modifier le fichier projet ») pour ouvrir le fichier dans un éditeur de texte et vérifiez que le fichier projet comprend maintenant le PackageReference NuGet pour « 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>
    

Code

  1. Dans le fichier code-behind App.xaml.cs, nous obtenons un handle vers Window en utilisant la méthode d’interopérabilité COM WinRT WindowNative.GetWindowHandle (voir Récupérer un handle de fenêtre (HWND)).

    Cette méthode est appelée à partir du gestionnaire OnLaunched de l’application, comme illustré ici :

    /// <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. Nous appelons ensuite une méthode SetWindowDetails, en passant le handle Window et les dimensions préférées. Pensez à ajouter la directive using static PInvoke.User32;.

    Dans cette méthode :

    • Nous appelons GetDpiForWindow pour obtenir la valeur en points par pouce (ppp) de la fenêtre (Win32 utilise les pixels réels tandis que WinUI 3 utilise les pixels effectifs). Cette valeur en ppp sert à calculer le facteur d’échelle et à l’appliquer à la largeur et à la hauteur spécifiées pour la fenêtre.
    • Nous appelons ensuite SetWindowPos pour spécifier l’emplacement souhaité de la fenêtre.
    • Enfin, nous appelons SetWindowLong pour désactiver les boutons Réduire et Agrandir.
    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. Dans le fichier MainWindow.xamlxaml, nous utilisons un ContentDialog avec un ScrollViewer pour afficher la liste de tous les modules chargés pour le processus en cours.

    <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. Nous remplaçons alors le gestionnaire d'événements MyButton_Click par le code suivant.

    Ici, nous obtenons une référence au processus en cours en appelant GetCurrentProcess. Nous allons ensuite itérer au sein de la collection de Modules et ajouter le nom de fichier de chaque ProcessModule à notre chaîne d’affichage.

    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. Compilez et exécutez l’application.

  6. Une fois que la fenêtre s’affiche, sélectionnez le bouton « Afficher les modules chargés ».

    Screenshot of the basic Win32 interop application described in this topic.
    Application interop Win32 de base décrite dans cette rubrique.

Résumé

Dans cette rubrique, nous avons abordé l’accès à l’implémentation d’une fenêtre sous-jacente (Win32 et HWND) et l’utilisation d’API Win32 avec les API WinRT. Nous avons ainsi montré comment vous pouvez utiliser du code d’application de bureau existant pour créer des applications de bureau WinUI 3.

Pour obtenir un exemple plus complet, consultez l’exemple de galerie AppWindow dans le dépôt GitHub des exemples du SDK d’application Windows.

Voir aussi