Freigeben über


Erstellen einer C#-.NET-App mit WinUI 3- und Win32-Interoperabilität

In diesem Thema erfahren Sie, wie Sie eine einfache C#-.NET-Anwendung mit WinUI 3- und Win32-Interoperabilitätsfunktionen mithilfe von Plattformaufrufdiensten (PInvoke) erstellen.

Prerequisites

  1. Starten der Entwicklung von Windows-Apps

Einfache verwaltete C#/.NET-App

In diesem Beispiel geben wir die Position und die Größe des App-Fensters an, konvertieren und skalieren sie für den entsprechenden DPI-Wert, deaktivieren die Schaltflächen zum Minimieren und Maximieren des Fensters und fragen schließlich den aktuellen Prozess ab, um eine Liste der darin geladenen Module anzuzeigen.

Wir erstellen unsere Beispiel-App aus der ursprünglichen Vorlagenanwendung (siehe Voraussetzungen). Siehe auch WinUI 3-Vorlagen in Visual Studio.

Die Datei "MainWindow.xaml"

Mit WinUI 3 können Sie Instanzen der Window-Klasse im XAML-Markup erstellen.

Die XAML Window-Klasse wurde erweitert, um Desktopfenster zu unterstützen, wodurch sie in eine Abstraktion der einzelnen Implementierungen auf niedriger Ebene umgewandelt wird, die von den UWP- und Desktop-App-Modellen verwendet werden. Insbesondere CoreWindow für UWP und Fensterhandles (oder HWNDs) für Win32.

Der folgende Code zeigt die Datei "MainWindow.xaml" aus der ursprünglichen Vorlagen-App, die die Window-Klasse als Stammelement für die App verwendet.

<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. Um Win32-APIs aufzurufen, die aus User32.dllexportiert wurden, können Sie den C#/Win32 P/Win32 P/Invoke Source Generator in Ihrem Visual Studio-Projekt verwenden. Klicken Sie auf "Tools>NuGet Package Manager>Manage NuGet Packages for Solution..." und (auf der Registerkarte " Durchsuchen ") nach Microsoft.Windows.CsWin32. Weitere Informationen finden Sie unter Aufrufen nativer Funktionen aus verwaltetem Code.

    Sie können optional bestätigen, dass die Installation erfolgreich war, indem Sie bestätigen, dass Microsoft.Windows.CsWin32 unter dem Knoten "Abhängigkeitspakete>" im Projektmappen-Explorer aufgeführt ist.

    Sie können auch optional auf die Anwendungsprojektdatei doppelklicken (oder mit der rechten Maustaste klicken und Projektdatei bearbeiten) auswählen, um die Datei in einem Text-Editor zu öffnen, und bestätigen Sie, dass die Projektdatei jetzt ein NuGet-PackageReference für "Microsoft.Windows.CsWin32" enthält.

    <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. Fügen Sie ihrem Projekt eine Textdatei hinzu, und nennen Sie sie NativeMethods.txt. Der Inhalt dieser Datei informiert den C#/Win32 P/Invoke Source Generator über die Funktionen und Typen, für die P/Invoke-Quellcode generiert werden soll. Mit anderen Worten, welche Funktionen und Typen Sie in Ihrem C#-Code aufrufen und verwenden.

    GetDpiForWindow
    GetWindowLong
    SetWindowPos
    SetWindowLong
    HWND_TOP
    WINDOW_STYLE
    

Code

  1. In der App.xaml.cs Code-Behind-Datei erhalten wir ein Handle für das Fenster über die WinRT COM-Interop-Methode WindowNative.GetWindowHandle (siehe Abrufen eines Fensterhandles (HWND)).

    Diese Methode wird aus dem OnLaunched-Handler der App aufgerufen, wie hier gezeigt:

    /// <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. Anschließend rufen wir eine SetWindowDetails-Methode auf und übergeben das Window-Handle und die bevorzugten Dimensionen.

    In dieser Methode:

    • Wir rufen GetDpiForWindow auf, um den Dpi-Wert (Dots per Inch) für das Fenster abzurufen (Win32 verwendet physische Pixel, während WinUI 3 effektive Pixel verwendet). Dieser DPI-Wert wird verwendet, um den Skalierungsfaktor zu berechnen und auf die für das Fenster angegebene Breite und Höhe anzuwenden.
    • Anschließend rufen wir SetWindowPos auf, um die gewünschte Position des Fensters anzugeben.
    • Schließlich rufen wir SetWindowLong auf, um die Schaltflächen " Minimieren " und " Maximieren " zu deaktivieren.
    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. In der Datei "MainWindow.xaml" verwenden wir einen ContentDialog mit einem ScrollViewer , um eine Liste aller Module anzuzeigen, die für den aktuellen Prozess geladen wurden.

    <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. Anschließend ersetzen wir den MyButton_Click Ereignishandler durch den folgenden Code.

    Hier erhalten wir einen Verweis auf den aktuellen Prozess durch Aufrufen von GetCurrentProcess. Anschließend durchlaufen wir die Sammlung von Modulen und fügen den Dateinamen jedes ProcessModule an unseren Anzeigestring an.

    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. Kompilieren sie die App, und führen Sie sie aus.

  6. Wählen Sie nach dem Anzeigen des Fensters die Schaltfläche "Geladene Module anzeigen" aus.

    Screenshot der in diesem Thema beschriebenen grundlegenden Win32-Interoperabilitätsanwendung.
    Die grundlegende Win32-Interoperabilitätsanwendung, die in diesem Thema beschrieben wird.

Summary

In diesem Thema haben wir den Zugriff auf die zugrunde liegende Fensterimplementierung (in diesem Fall Win32 und HWNDs) und die Verwendung von Win32-APIs zusammen mit den WinRT-APIs behandelt. Dies veranschaulicht, wie Sie vorhandenen Desktopanwendungscode beim Erstellen neuer WinUI 3-Desktop-Apps verwenden können.

Ein ausführlicheres Beispiel finden Sie im AppWindow-Galeriebeispiel in den Windows App SDK-Beispielen GitHub-Repo.

Ein Beispiel zum Anpassen der Fenstertitelleiste

In diesem zweiten Beispiel wird gezeigt, wie die Titelleiste des Fensters und deren Inhalt angepasst werden. Bevor Sie fortfahren, überprüfen Sie diese Themen:

Erstellen eines neuen Projekts

  1. Erstellen Sie in Visual Studio ein neues C# oder C++/WinRT-Projekt aus der Projektvorlage Leere App, gepackt (WinUI 3 in Desktop).

Configuration

  1. Verweisen Sie auch hier auf das Microsoft.Windows.CsWin32 NuGet-Paket genau wie im ersten Beispiel.

  2. Fügen Sie Ihrem Projekt eine NativeMethods.txt Textdatei hinzu.

    LoadImage
    SendMessage
    SetWindowText
    WM_SETICON
    

MainWindow.xaml

Note

Wenn Sie für diese exemplarische Vorgehensweise eine Symboldatei benötigen, können Sie die computer.ico Datei aus der WirelessHostednetwork Beispiel-App herunterladen. Platzieren Sie diese Datei in Ihrem Assets Ordner, und fügen Sie die Datei zu Ihrem Projekt als Inhalt hinzu. Sie können dann mithilfe der URL Assets/computer.icoauf die Datei verweisen.

Andernfalls können Sie eine Symboldatei verwenden, die Sie bereits haben, und die beiden Verweise auf sie in den folgenden Codeauflistungen ändern.

  1. In der folgenden Codeauflistung sehen Sie, dass wir in MainWindow.xaml zwei Schaltflächen hinzugefügt und jeweils Klickhandler angegeben haben. Im Click-Handler für die erste Schaltfläche (basicButton_Click) legen wir das Titelleistensymbol und den Text fest. Im zweiten (customButton_Click) zeigen wir eine größere Anpassung, indem die Titelleiste durch den Inhalt des StackPanel mit dem Namen customTitleBarPanel ersetzt wird.
<?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. In der folgenden Codeauflistung für den basicButton_Click-Handler , um die benutzerdefinierte Titelleiste ausgeblendet zu halten, reduzieren wir das customTitleBarPanelStackPanel, und wir legen die Eigenschaft "ExtendsContentIntoTitleBar " auf false.
  2. Anschließend rufen wir IWindowNative::get_WindowHandle (für C# mithilfe der Interop-Hilfsmethode GetWindowHandle) auf, um das Fensterhandle (HWND) des Hauptfensters abzurufen.
  3. Als Nächstes legen wir das Anwendungssymbol (für C# unter Verwendung des PInvoke.User32 NuGet-Pakets) fest, indem die Funktionen LoadImage und SendMessage aufgerufen werden.
  4. Schließlich rufen wir SetWindowText auf, um die Titelleistenzeichenfolge zu aktualisieren.
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. Im customButton_Click-Handler legen wir die Sichtbarkeit des customTitleBarPanel StackPanel auf Visible fest.
  2. Anschließend legen wir die Eigenschaft ExtendsContentIntoTitleBar auf true und rufen SetTitleBar auf, um das customTitleBarPanelStackPanel als unsere benutzerdefinierte Titelleiste anzuzeigen.
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. In der Datei App.xaml haben wir unmittelbar nach dem Kommentar <!-- Other app resources here --> einige benutzerdefinierte Pinsel für die Titelleiste hinzugefügt, wie unten gezeigt.
<?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. Wenn Sie diese Schritte in Ihrer eigenen App ausgeführt haben, können Sie ihr Projekt jetzt erstellen und die App ausführen. Es wird ein Anwendungsfenster ähnlich wie das folgende angezeigt (mit dem benutzerdefinierten App-Symbol):

    Vorlagen-App ohne Anpassung.
    Vorlagen-App.

  • Hier ist die grundlegende benutzerdefinierte Titelleiste:

    Vorlagen-App mit benutzerdefiniertem Anwendungssymbol.
    Vorlagen-App mit benutzerdefiniertem Anwendungssymbol.

  • Hier sehen Sie die vollständig benutzerdefinierte Titelleiste:

    Vorlagen-App mit benutzerdefinierter Titelleiste.
    Vorlagen-App mit benutzerdefinierter Titelleiste.

Siehe auch