Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Sie können Windows-Runtime Composition-APIs (auch als Visual layer) bezeichnet) in Ihren Windows Presentation Foundation-Apps (WPF) verwenden, um moderne Umgebungen zu erstellen, die für Windows Benutzer sichtbar sind.
Der vollständige Code für dieses Lernprogramm ist für GitHub verfügbar: WPF HelloComposition-Beispiel.
Voraussetzungen
Die UWP-XAML-Hosting-API hat diese Voraussetzungen.
- Wir gehen davon aus, dass Sie mit der App-Entwicklung mit WPF und UWP vertraut sind. Weitere Informationen finden Sie unter:
- .NET Framework 4.7.2 oder höher
- Windows 10 ab Version 1803
- Windows 10 SDK 17134 oder höher
Verwenden von Kompositions-APIs in WPF
In diesem Lernprogramm erstellen Sie eine einfache WPF App-UI und fügen diesem animierte Kompositionselemente hinzu. Sowohl die WPF- als auch die Kompositionskomponenten sind einfach gehalten, aber der dargestellte Interoperabilitätscode ist unabhängig von der Komplexität der Komponenten identisch. Die fertige App sieht wie folgt aus.
Erstellen Sie ein WPF-Projekt
Der erste Schritt besteht darin, das WPF App-Projekt zu erstellen, das eine Anwendungsdefinition und die XAML-Seite für die Benutzeroberfläche enthält.
So erstellen Sie ein neues WPF Application-Projekt in Visual C# mit dem Namen HelloComposition:
Öffne Visual Studio, und wähle Datei>Neu>Projekt aus.
Das Dialogfeld Neues Projekt wird geöffnet.
Erweitern Sie unter der Kategorie Installed den Knoten Visual C#, und wählen Sie dann Windows Desktop aus.
Wählen Sie die Vorlage WPF App (.NET Framework) aus.
Geben Sie den Namen HelloComposition ein, wählen Sie "Framework .NET Framework 4.7.2 aus, und klicken Sie dann auf OK.
Visual Studio erstellt das Projekt und öffnet den Designer für das Standardanwendungsfenster "MainWindow.xaml".
Konfigurieren des Projekts für die Verwendung der Windows-Runtime-APIs
Um Windows-Runtime (WinRT)-APIs in Ihrer WPF-App zu verwenden, müssen Sie Ihr Visual Studio Projekt für den Zugriff auf die Windows-Runtime konfigurieren. Darüber hinaus werden Vektoren umfassend von den Kompositions-APIs verwendet, daher müssen Sie die für die Verwendung von Vektoren erforderlichen Verweise hinzufügen.
NuGet-Pakete sind verfügbar, um beide Anforderungen zu erfüllen. Installieren Sie die neuesten Versionen dieser Pakete, um dem Projekt die erforderlichen Verweise hinzuzufügen.
- Microsoft.Windows. SDK. Verträge (Erfordert standardmäßiges Paketverwaltungsformat, das auf "PackageReference" festgelegt ist.)
- System.Numerics.Vectors
Note
Während wir die Verwendung der NuGet-Pakete zum Konfigurieren Ihres Projekts empfehlen, können Sie die erforderlichen Verweise manuell hinzufügen. Weitere Informationen finden Sie unter Verbessern Sie Ihre Desktop-Anwendung für Windows. In der folgenden Tabelle sind die Dateien aufgeführt, auf die Sie Verweise hinzufügen müssen.
| Datei | Standort |
|---|---|
| System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Programme (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.UniversalApiContract<version> |
| Windows. Foundation.FoundationContract.winmd | C:\Programme (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.FoundationContract<version> |
| System.Numerics.Vectors.dll | C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Numerics.Vectors\v4.0_4.0.0.0__b03f5f7f11d50a3a |
| System.Numerics.dll | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.2 |
Konfigurieren des Projekts für die Berücksichtigung von DPI-Werten pro Monitor
Der inhalt der visuellen Ebene, den Sie Ihrer App hinzufügen, wird nicht automatisch skaliert, um den DPI-Einstellungen des Bildschirms zu entsprechen, auf dem er angezeigt wird. Sie müssen die DPI-Sensibilisierung pro Monitor für Ihre App aktivieren und dann sicherstellen, dass der Code, den Sie zum Erstellen ihrer visuellen Layerinhalte verwenden, bei der Ausführung der App die aktuelle DPI-Skalierung berücksichtigt. Hier konfigurieren wir das Projekt so, dass die DPI-Werte beachtet werden. In späteren Abschnitten wird gezeigt, wie Sie die DPI-Informationen verwenden, um den Inhalt der visuellen Ebene zu skalieren.
WPF-Apps sind standardmäßig system-DPI-bewusst, müssen sich jedoch in einer app.manifest-Datei als monitor-DPI-bewusst deklarieren. So aktivieren Sie die dpi-Sensibilisierung auf Windows Ebene pro Monitor in der App-Manifestdatei:
Klicke im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt HelloComposition.
Wählen Sie im Kontextmenü "Neues Element hinzufügen>..." aus.
Wählen Sie im Dialogfeld " Neues Element hinzufügen " "Anwendungsmanifestdatei" aus, und klicken Sie dann auf "Hinzufügen". (Sie können den Standardnamen belassen.)
Suchen Sie in der Datei "app.manifest" dieses XML, und entkommentieren Sie es:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>Fügen Sie diese Einstellung hinter das öffnende
<windowsSettings>-Tag ein:<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>Außerdem müssen Sie die Einstellung "DoNotScaleForDpiChanges " in der App.config Datei festlegen.
Öffnen Sie App.Config, und fügen Sie diese XML im Element hinzu
<configuration>:<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>
Note
AppContextSwitchOverrides können nur einmal festgelegt werden. Wenn Ihre Anwendung bereits einen Satz aufweist, müssen Sie diesen Schalter innerhalb des Wertattributes durch Semikolon trennen.
(Weitere Informationen finden Sie im Per Monitor DPI-Entwickler-Handbuch und in den Beispielen auf GitHub.)
Erstellen einer von HwndHost abgeleiteten Klasse zum Hosten von Kompositionselementen
Zum Hosten von Inhalten, die Sie mit der visuellen Ebene erstellen, müssen Sie eine Klasse erstellen, die von HwndHost abgeleitet wird. Hier führen Sie die meisten Konfigurationen für das Hosten von Kompositions-APIs durch. In dieser Klasse verwenden Sie Platform Invocation Services (PInvoke) und COM Interop, um Kompositions-APIs in Ihre WPF-App zu integrieren. Weitere Informationen zu PInvoke und COM-Interoperabilität finden Sie unter Interoperieren mit nicht verwalteten Code.
Tipp
Überprüfe bei Bedarf am Ende des Tutorials den vollständigen Code, um sicherzustellen, dass sich der gesamte Code während des Durcharbeitens an den richtigen Stellen befindet.
Fügen Sie Ihrem Projekt eine neue Klassendatei hinzu, die von HwndHost abgeleitet wird.
- Klicke im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt HelloComposition.
- Wähle im Kontextmenü Hinzufügen>Klasse... aus.
- Geben Sie im Dialogfeld " Neues Element hinzufügen " den Namen der Klasse CompositionHost.cs, und klicken Sie dann auf "Hinzufügen".
Bearbeiten Sie in CompositionHost.cs die Klassendefinition, um von HwndHost abzuleiten.
// Add // using System.Windows.Interop; namespace HelloComposition { class CompositionHost : HwndHost { } }Fügen Sie der Klasse den folgenden Code und Konstruktor hinzu.
// Add // using Windows.UI.Composition; IntPtr hwndHost; int hostHeight, hostWidth; object dispatcherQueue; ICompositionTarget compositionTarget; public Compositor Compositor { get; private set; } public Visual Child { set { if (Compositor == null) { InitComposition(hwndHost); } compositionTarget.Root = value; } } internal const int WS_CHILD = 0x40000000, WS_VISIBLE = 0x10000000, LBS_NOTIFY = 0x00000001, HOST_ID = 0x00000002, LISTBOX_ID = 0x00000001, WS_VSCROLL = 0x00200000, WS_BORDER = 0x00800000; public CompositionHost(double height, double width) { hostHeight = (int)height; hostWidth = (int)width; }Überschreiben Sie die Methoden BuildWindowCore und DestroyWindowCore .
Note
In BuildWindowCore rufen Sie die Methoden InitializeCoreDispatcher und InitComposition auf. Du erstellst diese Methoden in den nächsten Schritten.
// Add // using System.Runtime.InteropServices; protected override HandleRef BuildWindowCore(HandleRef hwndParent) { // Create Window hwndHost = IntPtr.Zero; hwndHost = CreateWindowEx(0, "static", "", WS_CHILD | WS_VISIBLE, 0, 0, hostWidth, hostHeight, hwndParent.Handle, (IntPtr)HOST_ID, IntPtr.Zero, 0); // Create Dispatcher Queue dispatcherQueue = InitializeCoreDispatcher(); // Build Composition tree of content InitComposition(hwndHost); return new HandleRef(this, hwndHost); } protected override void DestroyWindowCore(HandleRef hwnd) { if (compositionTarget.Root != null) { compositionTarget.Root.Dispose(); } DestroyWindow(hwnd.Handle); }- CreateWindowEx und DestroyWindow erfordern eine PInvoke-Deklaration. Platzieren Sie diese Deklaration am Ende des Codes für die Klasse.
#region PInvoke declarations [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hwndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] internal static extern bool DestroyWindow(IntPtr hwnd); #endregion PInvoke declarationsInitialisieren sie einen Thread mit einem CoreDispatcher. Der Kernverteiler ist für die Verarbeitung von Fenstermeldungen und Verteilerereignissen für WinRT-APIs verantwortlich. Neue Instanzen von CoreDispatcher müssen in einem Thread erstellt werden, der über einen CoreDispatcher verfügt.
- Erstellen Sie eine Methode mit dem Namen InitializeCoreDispatcher , und fügen Sie Code zum Einrichten der Dispatcherwarteschlange hinzu.
private object InitializeCoreDispatcher() { DispatcherQueueOptions options = new DispatcherQueueOptions(); options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA; options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT; options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); object queue = null; CreateDispatcherQueueController(options, out queue); return queue; }- Auch für die Verteilerwarteschlange ist eine PInvoke-Deklaration erforderlich. Platzieren Sie diese Deklaration innerhalb des im vorherigen Schritt erstellten PInvoke-Deklarationsbereichs .
//typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE //{ // DQTAT_COM_NONE, // DQTAT_COM_ASTA, // DQTAT_COM_STA //}; internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE { DQTAT_COM_NONE = 0, DQTAT_COM_ASTA = 1, DQTAT_COM_STA = 2 }; //typedef enum DISPATCHERQUEUE_THREAD_TYPE //{ // DQTYPE_THREAD_DEDICATED, // DQTYPE_THREAD_CURRENT //}; internal enum DISPATCHERQUEUE_THREAD_TYPE { DQTYPE_THREAD_DEDICATED = 1, DQTYPE_THREAD_CURRENT = 2, }; //struct DispatcherQueueOptions //{ // DWORD dwSize; // DISPATCHERQUEUE_THREAD_TYPE threadType; // DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; //}; [StructLayout(LayoutKind.Sequential)] internal struct DispatcherQueueOptions { public int dwSize; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_TYPE threadType; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; }; //HRESULT CreateDispatcherQueueController( // DispatcherQueueOptions options, // ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController //); [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, [MarshalAs(UnmanagedType.IUnknown)] out object dispatcherQueueController);Sie haben nun die Dispatcher-Warteschlange bereit und können mit Initialisierung und Erstellung von Kompositionsinhalten beginnen.
Initialisieren Sie den Kompositor. Der Kompositor ist eine Fabrik, die eine Vielzahl von Typen im Windows.UI.Composition-Namespace erstellt, die visuelle Elemente, das Effektsystem und das Animationssystem umfassen. Die Compositor-Klasse verwaltet auch die Lebensdauer von Objekten, die von der Factory erstellt wurden.
private void InitComposition(IntPtr hwndHost) { ICompositorDesktopInterop interop; compositor = new Compositor(); object iunknown = compositor as object; interop = (ICompositorDesktopInterop)iunknown; IntPtr raw; interop.CreateDesktopWindowTarget(hwndHost, true, out raw); object rawObject = Marshal.GetObjectForIUnknown(raw); ICompositionTarget target = (ICompositionTarget)rawObject; if (raw == null) { throw new Exception("QI Failed"); } }- ICompositorDesktopInterop und ICompositionTarget erfordern COM-Importe. Platzieren Sie diesen Code nach der CompositionHost-Klasse , aber innerhalb der Namespacedeklaration.
#region COM Interop /* #undef INTERFACE #define INTERFACE ICompositorDesktopInterop DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807") { IFACEMETHOD(CreateDesktopWindowTarget)( _In_ HWND hwndTarget, _In_ BOOL isTopmost, _COM_Outptr_ IDesktopWindowTarget * *result ) PURE; }; */ [ComImport] [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ICompositorDesktopInterop { void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test); } //[contract(Windows.Foundation.UniversalApiContract, 2.0)] //[exclusiveto(Windows.UI.Composition.CompositionTarget)] //[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)] //interface ICompositionTarget : IInspectable //{ // [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value); // [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value); //} [ComImport] [Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")] [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] public interface ICompositionTarget { Windows.UI.Composition.Visual Root { get; set; } } #endregion COM Interop
Erstellen Sie ein UserControl, um Ihre Inhalte zum visuellen Baum der WPF hinzuzufügen.
Der letzte Schritt zum Einrichten der Infrastruktur, die zum Hosten von Kompositionsinhalten erforderlich ist, ist das Hinzufügen des HwndHost zur WPF visuellen Struktur.
Erstellen eines UserControl-Elements
Ein UserControl ist eine praktische Möglichkeit, Ihren Code zu verpacken, der Kompositionsinhalte erstellt und verwaltet, und den Inhalt einfach zu Ihrem XAML hinzuzufügen.
Fügen Sie Ihrem Projekt eine neue Benutzersteuerelementdatei hinzu.
- Klicke im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt HelloComposition.
- Wählen Sie im Kontextmenü die OptionBenutzersteuerung>... aus.
- Benennen Sie im Dialogfeld "Neues Element hinzufügen " das Benutzersteuerelement CompositionHostControl.xaml, und klicken Sie dann auf "Hinzufügen".
Sowohl die CompositionHostControl.xaml- als auch CompositionHostControl.xaml.cs Dateien werden erstellt und dem Projekt hinzugefügt.
Ersetzen Sie in CompositionHostControl.xaml die
<Grid> </Grid>Tags durch dieses Border-Element , bei dem es sich um den XAML-Container handelt, in den Ihr HwndHost wechselt.<Border Name="CompositionHostElement"/>
Im Code für das Benutzersteuerelement erstellen Sie eine Instanz der CompositionHost-Klasse, die Sie im vorherigen Schritt erstellt haben, und fügen sie als untergeordnetes Element von CompositionHostElement hinzu, den Rahmen, den Sie auf der XAML-Seite erstellt haben.
Fügen Sie in CompositionHostControl.xaml.cs private Variablen für die Objekte hinzu, die Sie im Kompositionscode verwenden. Fügen Sie diese nach der Klassendefinition hinzu.
CompositionHost compositionHost; Compositor compositor; Windows.UI.Composition.ContainerVisual containerVisual; DpiScale currentDpi;Fügen Sie einen Handler für das Loaded-Ereignis des Benutzersteuerelements hinzu. Hier richten Sie Ihre CompositionHost-Instanz ein.
- Verbinden Sie im Konstruktor den Ereignishandler wie hier gezeigt (
Loaded += CompositionHostControl_Loaded;).
public CompositionHostControl() { InitializeComponent(); Loaded += CompositionHostControl_Loaded; }- Fügen Sie die Ereignishandlermethode mit dem Namen CompositionHostControl_Loaded hinzu.
private void CompositionHostControl_Loaded(object sender, RoutedEventArgs e) { // If the user changes the DPI scale setting for the screen the app is on, // the CompositionHostControl is reloaded. Don't redo this set up if it's // already been done. if (compositionHost is null) { currentDpi = VisualTreeHelper.GetDpi(this); compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost; compositor = compositionHost.Compositor; containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual; } }In dieser Methode richten Sie die Objekte ein, die Sie im Kompositionscode verwenden. Hier ist ein kurzer Blick darauf, was passiert.
- Stellen Sie zunächst sicher, dass die Einrichtung nur einmal erfolgt, indem Sie überprüfen, ob bereits eine Instanz von CompositionHost vorhanden ist.
// If the user changes the DPI scale setting for the screen the app is on, // the CompositionHostControl is reloaded. Don't redo this set up if it's // already been done. if (compositionHost is null) { }- Rufen Sie den aktuellen DPI-Wert ab. Dies wird verwendet, um die Kompositionselemente ordnungsgemäß zu skalieren.
currentDpi = VisualTreeHelper.GetDpi(this);- Erstellen Sie eine Instanz von CompositionHost und ordnen Sie sie als untergeordnetes Element dem Rahmen CompositionHostElement zu.
compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost;- Rufen Sie den Compositor aus dem CompositionHost ab.
compositor = compositionHost.Compositor;- Verwenden Sie den Kompositor, um ein visuelles Containerobjekt zu erstellen. Dies ist der Kompositionscontainer, dem Sie Ihre Composition-Elemente hinzufügen.
containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual;- Verbinden Sie im Konstruktor den Ereignishandler wie hier gezeigt (
Hinzufügen von Composition-Elementen
Nachdem du die Infrastruktur eingerichtet hast, kannst du nun den Composition-Inhalt generieren, den du anzeigen möchtest.
In diesem Beispiel fügen Sie Code hinzu, der ein einfaches quadratisches SpriteVisual erstellt und animiert.
Füge ein Kompositionselement hinzu. Fügen Sie in CompositionHostControl.xaml.cs diese Methoden der CompositionHostControl-Klasse hinzu.
// Add // using System.Numerics; public void AddElement(float size, float offsetX, float offsetY) { var visual = compositor.CreateSpriteVisual(); visual.Size = new Vector2(size, size); visual.Scale = new Vector3((float)currentDpi.DpiScaleX, (float)currentDpi.DpiScaleY, 1); visual.Brush = compositor.CreateColorBrush(GetRandomColor()); visual.Offset = new Vector3(offsetX * (float)currentDpi.DpiScaleX, offsetY * (float)currentDpi.DpiScaleY, 0); containerVisual.Children.InsertAtTop(visual); AnimateSquare(visual, 3); } private void AnimateSquare(SpriteVisual visual, int delay) { float offsetX = (float)(visual.Offset.X); // Already adjusted for DPI. // Adjust values for DPI scale, then find the Y offset that aligns the bottom of the square // with the bottom of the host container. This is the value to animate to. var hostHeightAdj = CompositionHostElement.ActualHeight * currentDpi.DpiScaleY; var squareSizeAdj = visual.Size.Y * currentDpi.DpiScaleY; float bottom = (float)(hostHeightAdj - squareSizeAdj); // Create the animation only if it's needed. if (visual.Offset.Y != bottom) { Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation(); animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f)); animation.Duration = TimeSpan.FromSeconds(2); animation.DelayTime = TimeSpan.FromSeconds(delay); visual.StartAnimation("Offset", animation); } } private Windows.UI.Color GetRandomColor() { Random random = new Random(); byte r = (byte)random.Next(0, 255); byte g = (byte)random.Next(0, 255); byte b = (byte)random.Next(0, 255); return Windows.UI.Color.FromArgb(255, r, g, b); }
Umgang mit DPI-Änderungen
Der Code zum Hinzufügen und Animieren eines Elements berücksichtigt die aktuelle DPI-Skalierung, wenn Elemente erstellt werden. Sie müssen jedoch auch dpi-Änderungen berücksichtigen, während die App ausgeführt wird. Sie können das HwndHost.DpiChanged-Ereignis behandeln, um über Änderungen benachrichtigt zu werden und Ihre Berechnungen basierend auf dem neuen DPI-Wert anzupassen.
Fügen Sie in der CompositionHostControl_Loaded-Methode nach der letzten Zeile dies hinzu, um den DpiChanged-Ereignishandler zu verbinden.
compositionHost.DpiChanged += CompositionHost_DpiChanged;Fügen Sie die Ereignishandlermethode mit dem Namen CompositionHostDpiChanged hinzu. Dieser Code passt die Skalierung und den Offset der einzelnen Elemente an und berechnet alle Animationen, die nicht abgeschlossen sind.
private void CompositionHost_DpiChanged(object sender, DpiChangedEventArgs e) { currentDpi = e.NewDpi; Vector3 newScale = new Vector3((float)e.NewDpi.DpiScaleX, (float)e.NewDpi.DpiScaleY, 1); foreach (SpriteVisual child in containerVisual.Children) { child.Scale = newScale; var newOffsetX = child.Offset.X * ((float)e.NewDpi.DpiScaleX / (float)e.OldDpi.DpiScaleX); var newOffsetY = child.Offset.Y * ((float)e.NewDpi.DpiScaleY / (float)e.OldDpi.DpiScaleY); child.Offset = new Vector3(newOffsetX, newOffsetY, 1); // Adjust animations for DPI change. AnimateSquare(child, 0); } }
Hinzufügen des Benutzersteuerelements zur XAML-Seite
Jetzt können Sie das Benutzersteuerelement zur XAML-Benutzeroberfläche hinzufügen.
Legen Sie in "MainWindow.xaml" die Fensterhöhe auf 600 und die Breite auf 840 fest.
Fügen Sie den XAML-Code für die Benutzeroberfläche hinzu. Fügen Sie in "MainWindow.xaml" diesen XAML-Code zwischen den Stammtags
<Grid> </Grid>hinzu.<Grid.ColumnDefinitions> <ColumnDefinition Width="210"/> <ColumnDefinition Width="600"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="46"/> <RowDefinition/> </Grid.RowDefinitions> <Button Content="Add composition element" Click="Button_Click" Grid.Row="1" Margin="12,0" VerticalAlignment="Top" Height="40"/> <TextBlock Text="Composition content" FontSize="20" Grid.Column="1" Margin="0,12,0,4" HorizontalAlignment="Center"/> <local:CompositionHostControl x:Name="CompositionHostControl1" Grid.Row="1" Grid.Column="1" VerticalAlignment="Top" Width="600" Height="500" BorderBrush="LightGray" BorderThickness="3"/>Bearbeiten Sie das Klicken auf die Schaltfläche, um neue Elemente zu erstellen. (Das Click-Ereignis ist bereits im XAML-Code eingebunden.)
Fügen Sie in MainWindow.xaml.cs diese Button_Click Ereignishandlermethode hinzu. Dieser Code ruft CompositionHost.AddElement auf, um ein neues Element mit einer zufällig generierten Größe und einem Offset zu erstellen.
// Add // using System; private void Button_Click(object sender, RoutedEventArgs e) { Random random = new Random(); float size = random.Next(50, 150); float offsetX = random.Next(0, (int)(CompositionHostControl1.ActualWidth - size)); float offsetY = random.Next(0, (int)(CompositionHostControl1.ActualHeight/2 - size)); CompositionHostControl1.AddElement(size, offsetX, offsetY); }
Sie können jetzt Ihre WPF-App erstellen und ausführen. Überprüfe ggf. den vollständigen Code am Ende des Tutorials, um sicherzustellen, dass sich der gesamte Code an den richtigen Stellen befindet.
Wenn du die App ausführst und auf den Button klickst, solltest du sehen, wie animierte Quadrate zur Benutzeroberfläche hinzugefügt werden.
Nächste Schritte
Ein vollständigeres Beispiel, das auf derselben Infrastruktur aufbaut, finden Sie im WPF Visual Layer Integration Sample auf GitHub.
Weitere Ressourcen
- Getting Started (WPF) (.NET)
- Interoperieren mit nicht verwalteten Code (.NET)
- Erste Schritte mit Windows-Apps (UWP)
- Verbessern Ihrer Desktopanwendung für Windows (UWP)
- Windows.UI.Composition-Namespace (UWP)
Vollständiger Code
Hier ist der vollständige Code für dieses Lernprogramm.
MainWindow.xaml
<Window x:Class="HelloComposition.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HelloComposition"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="840">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210"/>
<ColumnDefinition Width="600"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="46"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add composition element" Click="Button_Click"
Grid.Row="1" Margin="12,0"
VerticalAlignment="Top" Height="40"/>
<TextBlock Text="Composition content" FontSize="20"
Grid.Column="1" Margin="0,12,0,4"
HorizontalAlignment="Center"/>
<local:CompositionHostControl x:Name="CompositionHostControl1"
Grid.Row="1" Grid.Column="1"
VerticalAlignment="Top"
Width="600" Height="500"
BorderBrush="LightGray" BorderThickness="3"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace HelloComposition
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Random random = new Random();
float size = random.Next(50, 150);
float offsetX = random.Next(0, (int)(CompositionHostControl1.ActualWidth - size));
float offsetY = random.Next(0, (int)(CompositionHostControl1.ActualHeight/2 - size));
CompositionHostControl1.AddElement(size, offsetX, offsetY);
}
}
}
CompositionHostControl.xaml
<UserControl x:Class="HelloComposition.CompositionHostControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HelloComposition"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border Name="CompositionHostElement"/>
</UserControl>
CompositionHostControl.xaml.cs
using System;
using System.Numerics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Windows.UI.Composition;
namespace HelloComposition
{
/// <summary>
/// Interaction logic for CompositionHostControl.xaml
/// </summary>
public partial class CompositionHostControl : UserControl
{
CompositionHost compositionHost;
Compositor compositor;
Windows.UI.Composition.ContainerVisual containerVisual;
DpiScale currentDpi;
public CompositionHostControl()
{
InitializeComponent();
Loaded += CompositionHostControl_Loaded;
}
private void CompositionHostControl_Loaded(object sender, RoutedEventArgs e)
{
// If the user changes the DPI scale setting for the screen the app is on,
// the CompositionHostControl is reloaded. Don't redo this set up if it's
// already been done.
if (compositionHost is null)
{
currentDpi = VisualTreeHelper.GetDpi(this);
compositionHost = new CompositionHost(CompositionHostElement.ActualHeight, CompositionHostElement.ActualWidth);
CompositionHostElement.Child = compositionHost;
compositor = compositionHost.Compositor;
containerVisual = compositor.CreateContainerVisual();
compositionHost.Child = containerVisual;
}
}
protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
{
base.OnDpiChanged(oldDpi, newDpi);
currentDpi = newDpi;
Vector3 newScale = new Vector3((float)newDpi.DpiScaleX, (float)newDpi.DpiScaleY, 1);
foreach (SpriteVisual child in containerVisual.Children)
{
child.Scale = newScale;
var newOffsetX = child.Offset.X * ((float)newDpi.DpiScaleX / (float)oldDpi.DpiScaleX);
var newOffsetY = child.Offset.Y * ((float)newDpi.DpiScaleY / (float)oldDpi.DpiScaleY);
child.Offset = new Vector3(newOffsetX, newOffsetY, 1);
// Adjust animations for DPI change.
AnimateSquare(child, 0);
}
}
public void AddElement(float size, float offsetX, float offsetY)
{
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2(size, size);
visual.Scale = new Vector3((float)currentDpi.DpiScaleX, (float)currentDpi.DpiScaleY, 1);
visual.Brush = compositor.CreateColorBrush(GetRandomColor());
visual.Offset = new Vector3(offsetX * (float)currentDpi.DpiScaleX, offsetY * (float)currentDpi.DpiScaleY, 0);
containerVisual.Children.InsertAtTop(visual);
AnimateSquare(visual, 3);
}
private void AnimateSquare(SpriteVisual visual, int delay)
{
float offsetX = (float)(visual.Offset.X); // Already adjusted for DPI.
// Adjust values for DPI scale, then find the Y offset that aligns the bottom of the square
// with the bottom of the host container. This is the value to animate to.
var hostHeightAdj = CompositionHostElement.ActualHeight * currentDpi.DpiScaleY;
var squareSizeAdj = visual.Size.Y * currentDpi.DpiScaleY;
float bottom = (float)(hostHeightAdj - squareSizeAdj);
// Create the animation only if it's needed.
if (visual.Offset.Y != bottom)
{
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f));
animation.Duration = TimeSpan.FromSeconds(2);
animation.DelayTime = TimeSpan.FromSeconds(delay);
visual.StartAnimation("Offset", animation);
}
}
private Windows.UI.Color GetRandomColor()
{
Random random = new Random();
byte r = (byte)random.Next(0, 255);
byte g = (byte)random.Next(0, 255);
byte b = (byte)random.Next(0, 255);
return Windows.UI.Color.FromArgb(255, r, g, b);
}
}
}
CompositionHost.cs
using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using Windows.UI.Composition;
namespace HelloComposition
{
class CompositionHost : HwndHost
{
IntPtr hwndHost;
int hostHeight, hostWidth;
object dispatcherQueue;
ICompositionTarget compositionTarget;
public Compositor Compositor { get; private set; }
public Visual Child
{
set
{
if (Compositor == null)
{
InitComposition(hwndHost);
}
compositionTarget.Root = value;
}
}
internal const int
WS_CHILD = 0x40000000,
WS_VISIBLE = 0x10000000,
LBS_NOTIFY = 0x00000001,
HOST_ID = 0x00000002,
LISTBOX_ID = 0x00000001,
WS_VSCROLL = 0x00200000,
WS_BORDER = 0x00800000;
public CompositionHost(double height, double width)
{
hostHeight = (int)height;
hostWidth = (int)width;
}
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
// Create Window
hwndHost = IntPtr.Zero;
hwndHost = CreateWindowEx(0, "static", "",
WS_CHILD | WS_VISIBLE,
0, 0,
hostWidth, hostHeight,
hwndParent.Handle,
(IntPtr)HOST_ID,
IntPtr.Zero,
0);
// Create Dispatcher Queue
dispatcherQueue = InitializeCoreDispatcher();
// Build Composition Tree of content
InitComposition(hwndHost);
return new HandleRef(this, hwndHost);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
if (compositionTarget.Root != null)
{
compositionTarget.Root.Dispose();
}
DestroyWindow(hwnd.Handle);
}
private object InitializeCoreDispatcher()
{
DispatcherQueueOptions options = new DispatcherQueueOptions();
options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA;
options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT;
options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
object queue = null;
CreateDispatcherQueueController(options, out queue);
return queue;
}
private void InitComposition(IntPtr hwndHost)
{
ICompositorDesktopInterop interop;
Compositor = new Compositor();
object iunknown = Compositor as object;
interop = (ICompositorDesktopInterop)iunknown;
IntPtr raw;
interop.CreateDesktopWindowTarget(hwndHost, true, out raw);
object rawObject = Marshal.GetObjectForIUnknown(raw);
compositionTarget = (ICompositionTarget)rawObject;
if (raw == null) { throw new Exception("QI Failed"); }
}
#region PInvoke declarations
//typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
//{
// DQTAT_COM_NONE,
// DQTAT_COM_ASTA,
// DQTAT_COM_STA
//};
internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
{
DQTAT_COM_NONE = 0,
DQTAT_COM_ASTA = 1,
DQTAT_COM_STA = 2
};
//typedef enum DISPATCHERQUEUE_THREAD_TYPE
//{
// DQTYPE_THREAD_DEDICATED,
// DQTYPE_THREAD_CURRENT
//};
internal enum DISPATCHERQUEUE_THREAD_TYPE
{
DQTYPE_THREAD_DEDICATED = 1,
DQTYPE_THREAD_CURRENT = 2,
};
//struct DispatcherQueueOptions
//{
// DWORD dwSize;
// DISPATCHERQUEUE_THREAD_TYPE threadType;
// DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
//};
[StructLayout(LayoutKind.Sequential)]
internal struct DispatcherQueueOptions
{
public int dwSize;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_TYPE threadType;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
};
//HRESULT CreateDispatcherQueueController(
// DispatcherQueueOptions options,
// ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController
//);
[DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options,
[MarshalAs(UnmanagedType.IUnknown)]
out object dispatcherQueueController);
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
#endregion PInvoke declarations
}
#region COM Interop
/*
#undef INTERFACE
#define INTERFACE ICompositorDesktopInterop
DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807")
{
IFACEMETHOD(CreateDesktopWindowTarget)(
_In_ HWND hwndTarget,
_In_ BOOL isTopmost,
_COM_Outptr_ IDesktopWindowTarget * *result
) PURE;
};
*/
[ComImport]
[Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICompositorDesktopInterop
{
void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test);
}
//[contract(Windows.Foundation.UniversalApiContract, 2.0)]
//[exclusiveto(Windows.UI.Composition.CompositionTarget)]
//[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)]
//interface ICompositionTarget : IInspectable
//{
// [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value);
// [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value);
//}
[ComImport]
[Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface ICompositionTarget
{
Windows.UI.Composition.Visual Root
{
get;
set;
}
}
#endregion COM Interop
}