Budowanie aplikacji C# .NET z użyciem technologii WinUI 3 i interoperacyjności Win32
W tym temacie krok po kroku przechodzimy przez proces tworzenia podstawowej aplikacji C# .NET z wykorzystaniem WinUI 3 i funkcjonalności międzyoperacyjnych Win32 przy użyciu Usług wywoływania platformy (PInvoke).
Warunki wstępne
Podstawowa zarządzana aplikacja C#/.NET
W tym przykładzie określimy lokalizację i rozmiar okna aplikacji, przekonwertujemy i przeskalujemy je pod kątem odpowiedniego DPI, wyłączymy przyciski minimalizacji i maksymalizacji okna, a na koniec przeprowadzimy zapytanie na aktualnym procesie, aby wyświetlić listę modułów załadowanych do niego.
Utworzymy przykładową aplikację z początkowego szablonu aplikacji (zobacz Prerequisites). Zobacz również szablony WinUI 3 w programie Visual Studio.
Plik MainWindow.xaml
Dzięki WinUI 3 można tworzyć wystąpienia klasy Window w znacznikach XAML.
Klasa Window XAML została rozszerzona w celu obsługi okien pulpitu, przekształcając ją w abstrakcję dla każdej z implementacji okien niskiego poziomu używanych przez modele aplikacji UWP oraz aplikacji desktopowych. W szczególności CoreWindow dla platformy UWP i uchwytów okien (lub HWND) dla platformy Win32.
Poniższy kod przedstawia plik MainWindow.xaml z początkowej aplikacji szablonu, który używa klasy Okna jako elementu głównego aplikacji.
<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>
Konfiguracja
Aby wywołać interfejsy API Win32 wyeksportowane z
User32.dll
, możesz użyć C#/Win32 P/Invoke Source Generator w projekcie programu Visual Studio. Kliknij Tools>NuGet Package Manager>Zarządzaj pakietami NuGet dla rozwiązania...i (na karcie Przeglądaj) wyszukaj Microsoft.Windows.CsWin32. Aby uzyskać więcej informacji, zobacz Wywoływanie funkcji natywnych z kodu zarządzanego.Opcjonalnie możesz potwierdzić, że instalacja zakończyła się pomyślnie, potwierdzając, że Microsoft.Windows.CsWin32 znajduje się na liście w obszarze zależności >pakietów w Eksploratorze rozwiązań.
Opcjonalnie możesz również kliknąć dwukrotnie plik projektu aplikacji (lub kliknąć prawym przyciskiem myszy i wybrać Edytuj plik projektu), aby otworzyć plik w edytorze tekstów i potwierdzić, że plik projektu zawiera teraz
PackageReference
NuGet dla "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>
Dodaj plik tekstowy do projektu i nadaj mu nazwę
NativeMethods.txt
. Zawartość tego pliku informuje Generator Źródła P/Invoke dla C#/Win32 o funkcjach i typach, dla których chcesz wygenerować kod źródłowy P/Invoke. Innymi słowy, które funkcje i typy będą wywoływane i używane w kodzie języka C#.GetDpiForWindow GetWindowLong SetWindowPos SetWindowLong HWND_TOP WINDOW_STYLE
Kod
W pliku zaplecza kodu
App.xaml.cs
uzyskujemy uchwyt do Okno, korzystając z metody międzyoperacyjności WinRT COM WindowNative.GetWindowHandle (zobacz Pobieranie uchwytu okna (HWND)).Ta metoda jest wywoływana z programu obsługi aplikacji OnLaunched, jak pokazano tutaj:
/// <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(); }
Następnie wywołujemy metodę
SetWindowDetails
, przekazując preferowane wymiary i uchwyt okna.W tej metodzie:
- Wywołujemy GetDpiForWindow, aby uzyskać wartość kropek na cal (dpi) dla okna (Win32 używa pikseli fizycznych, podczas gdy winUI 3 używa efektywnych pikseli). Ta wartość dpi służy do obliczania współczynnika skalowania i stosowania jej do szerokości i wysokości określonej dla okna.
- Następnie wywołujemy SetWindowPos, aby określić żądaną lokalizację okna.
- Na koniec wywołujemy SetWindowLong, aby wyłączyć przyciski Minimalizuj i Maksymalizuj.
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); }
W pliku MainWindow.xaml używamy ContentDialog z ScrollViewer, aby wyświetlić listę wszystkich modułów załadowanych do bieżącego procesu.
<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>
Następnie zastąpimy program obsługi zdarzeń
MyButton_Click
następującym kodem.W tym miejscu uzyskujemy odwołanie do bieżącego procesu przez wywołanie metody GetCurrentProcess. Następnie wykonujemy iterację po kolekcji Modułów i dołączamy nazwę pliku każdego modułu procesu do naszego ciągu znaków wyświetlanego.
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(); }
Skompiluj i uruchom aplikację.
Po wyświetleniu okna wybierz przycisk "Wyświetl załadowane moduły".
Podstawowa aplikacja międzyoperacka Win32 opisana w tym temacie.
Streszczenie
W tym temacie omówiono uzyskiwanie dostępu do podstawowej implementacji okien (w tym przypadku Win32 i HWND) oraz używanie interfejsów API Win32 wraz z interfejsami API WinRT. Pokazuje to, jak można używać istniejącego kodu aplikacji desktopowych podczas tworzenia nowych aplikacji desktopowych WinUI 3.
Aby zapoznać się z bardziej rozbudowanym przykładem, zobacz przykład z galerii AppWindow w repozytorium GitHub Windows App SDK Samples .
Przykład dostosowywania paska tytułu okna
W tym drugim przykładzie pokazano, jak dostosować pasek tytułu okna i jego zawartość. Przed wykonaniem tych czynności zapoznaj się z następującymi tematami:
Tworzenie nowego projektu
- W programie Visual Studio utwórz nowy projekt C# lub C++/WinRT z szablonu projektu Blank App, Packaged (WinUI 3 in Desktop).
Konfiguracja
Ponownie odwołaj się do pakietu NuGet Microsoft.Windows.CsWin32 tak jak w pierwszym przykładzie.
Dodaj plik tekstowy
NativeMethods.txt
do swojego projektu.LoadImage SendMessage SetWindowText WM_SETICON
MainWindow.xaml
Uwaga
Jeśli potrzebujesz pliku ikony do użycia z tym przewodnikiem, możesz pobrać plik computer.ico
z przykładowej aplikacji WirelessHostednet work. Umieść ten plik w folderze Assets
i dodaj plik do projektu jako zawartość. Następnie będzie można odwoływać się do pliku przy użyciu adresu URL Assets/computer.ico
.
W przeciwnym razie możesz użyć już posiadanego pliku ikony i zmienić dwa odwołania do niego w poniższych listach kodu.
- W poniższym fragmencie kodu zobaczysz, że w
MainWindow.xaml
dodaliśmy dwa przyciski i określiliśmy programy obsługi zdarzeń Click dla każdego. W programie obsługi Kliknij dla pierwszego przycisku (basicButton_Click) ustawiamy ikonę paska tytułu i tekst. W drugim przypadku (customButton_Click) demonstrujemy bardziej znaczące dostosowanie, zastępując pasek tytułu treścią StackPanel o nazwie 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
- W poniższym kodzie programu obsługi basicButton_Click — aby utrzymać niestandardowy pasek tytułu w ukryciu — zwijamy customTitleBarPanelStackPaneli ustawiamy właściwość ExtendsContentIntoTitleBar na
false
. - Następnie wywołujemy metodę IWindowNative::get_WindowHandle (dla języka C#, używając metody pomocnika międzyoperacyjnego GetWindowHandle) w celu pobrania dojścia okna (HWND) głównego okna.
- Następnie ustawiliśmy ikonę aplikacji (dla języka C#, używając funkcji PInvoke.User32 Pakietu NuGet), wywołując funkcje LoadImage i SendMessage.
- Na koniec wywołujemy SetWindowText, aby zaktualizować ciąg paska tytułu.
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");
}
- W obsłudze zdarzenia customButton_Click ustawiamy widoczność customTitleBarPanelStackPanel na Visible.
- Następnie ustawimy właściwość ExtendsContentIntoTitleBar na
true
i wywołamy SetTitleBar, aby wyświetlić customTitleBarPanelStackPanel jako niestandardowy pasek tytułu.
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
- W pliku
App.xaml
, bezpośrednio po komentarzu<!-- Other app resources here -->
, dodaliśmy kilka niestandardowych kolorowych pędzli dla paska tytułu, jak pokazano poniżej.
<?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>
Jeśli wykonano kroki opisane we własnej aplikacji, możesz teraz skompilować projekt i uruchomić aplikację. Zostanie wyświetlone okno aplikacji podobne do poniższego (z ikoną aplikacji niestandardowej):
Aplikacja szablonu .
Oto podstawowy niestandardowy pasek tytułu:
Aplikacja wzorcowa z niestandardową ikoną aplikacji.Oto w pełni niestandardowy pasek tytułu:
aplikacja szablonu z niestandardowym paskiem tytułu.