Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Vous pouvez utiliser les API de composition de Windows Runtime (également appelées couche Visual) dans vos applications Windows Presentation Foundation (WPF) pour créer des expériences modernes qui captivent les utilisateurs de Windows.
Le code complet de ce didacticiel est disponible sur GitHub : WPF exemple HelloComposition.
Prerequisites
L’API d’hébergement XAML UWP présente ces prérequis.
- Nous partons du principe que vous connaissez le développement d’applications à l’aide de WPF et UWP. Pour plus d’informations, consultez :
- .NET Framework 4.7.2 ou version ultérieure
- Windows 10, version 1803 ou ultérieure
- SDK Windows 10 17134 ou version ultérieure
Comment utiliser des API de composition dans WPF
Dans ce tutoriel, vous allez créer une interface utilisateur d’application simple WPF et y ajouter des éléments de composition animés. Les composants WPF et Composition sont simples, mais le code d’interopérabilité indiqué est le même, quelle que soit la complexité des composants. L’application terminée ressemble à ceci.
Créer un projet WPF
La première étape consiste à créer le projet d’application WPF, qui inclut une définition d’application et la page XAML de l’interface utilisateur.
Pour créer un projet d’application WPF dans Visual C# nommé HelloComposition :
Ouvrez Visual Studio, puis sélectionnez Fichier>Nouveau>Projet.
La boîte de dialogue Nouveau projet s’affiche.
Sous la catégorie Installed, développez le nœud Visual C#, puis sélectionnez Windows Desktop.
Sélectionnez le modèle WPF App (.NET Framework).
Entrez le nom HelloComposition, sélectionnez Framework .NET Framework 4.7.2, puis cliquez sur OK.
Visual Studio crée le projet et ouvre le concepteur pour la fenêtre d’application par défaut nommée MainWindow.xaml.
Configurer le projet pour qu’il utilise des API Windows Runtime
Pour utiliser des API Windows Runtime (WinRT) dans votre application WPF, vous devez configurer votre projet Visual Studio pour accéder au Windows Runtime. En outre, les vecteurs sont largement utilisés par les API Composition. Vous devez donc ajouter les références requises pour utiliser des vecteurs.
Les packages NuGet sont disponibles pour répondre à ces deux besoins. Installez les dernières versions de ces packages pour ajouter les références nécessaires à votre projet.
- Microsoft.Windows. SDK. Contracts (Nécessite le format de gestion de package par défaut défini sur PackageReference.)
- System.Numerics.Vectors
Note
Bien que nous vous recommandons d’utiliser les packages NuGet pour configurer votre projet, vous pouvez ajouter manuellement les références requises. Pour plus d’informations, consultez Enhance votre application de bureau pour Windows. Le tableau suivant présente les fichiers auxquels vous devez ajouter des références.
| File | Emplacement |
|---|---|
| System.Runtime.WindowsRuntime | C :\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.UniversalApiContract<version> |
| Windows. Foundation.FoundationContract.winmd | C :\Program Files (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)\Assemblys de référence\Microsoft\Framework.NETFramework\v4.7.2 |
Configurer le projet pour qu’il soit conscient du DPI par moniteur
Le contenu de la couche visuelle que vous ajoutez à votre application ne s'adapte pas automatiquement aux paramètres DPI de l'écran sur lequel il est affiché. Vous devez activer la prise en charge DPI par écran pour votre application, puis vous assurer que le code que vous utilisez pour créer le contenu visuel de votre application tient compte de l’échelle DPI actuelle lorsque l’application est en cours d’exécution. Ici, nous configurons le projet pour qu’il soit conscient des ppp. Dans les sections ultérieures, nous montrons comment utiliser les informations DPI pour mettre à l’échelle le contenu de la couche visuelle.
Les applications WPF sont conscientes par défaut du PPP système, mais doivent se déclarer pour être conscientes du PPP par moniteur dans un fichier app.manifest. Pour activer la prise en charge par Windows de la sensibilité DPI par écran dans le fichier manifeste de l’application :
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet HelloComposition.
Dans le menu contextuel, sélectionnez Ajouter>un nouvel élément....
Dans la boîte de dialogue Ajouter un nouvel élément , sélectionnez « Fichier manifeste d’application », puis cliquez sur Ajouter. (Vous pouvez laisser le nom par défaut.)
Dans le fichier app.manifest, recherchez ce fichier xml et annulez le commentaire :
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>Ajoutez ce paramètre après la balise d’ouverture
<windowsSettings>:<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>Vous devez également définir le paramètre DoNotScaleForDpiChanges dans le fichier App.config.
Ouvrez App.Config et ajoutez ce xml à l’intérieur de l’élément
<configuration>:<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>
Note
AppContextSwitchOverrides ne peut être défini qu’une seule fois. Si votre application a déjà un ensemble, vous devez délimiter ce commutateur à l’intérieur de l’attribut valeur.
(Pour plus d’informations, consultez le Per Monitor DPI Developer Guide and samples on GitHub.)
Créer une classe dérivée HwndHost pour héberger des éléments de composition
Pour héberger du contenu que vous créez avec la couche visuelle, vous devez créer une classe qui dérive de HwndHost. C’est là que vous effectuez la plupart de la configuration pour l’hébergement d’API composition. Dans cette classe, vous utilisez Platform Invocation Services (PInvoke) et COM Interop pour intégrer les API composition dans votre application WPF. Pour plus d’informations sur PInvoke et COM Interop, consultez Interopération avec du code non managé.
Conseil / Astuce
Si nécessaire, consultez le code complet à la fin du tutoriel pour vous assurer que tout le code se trouve à la bonne place à mesure que vous parcourez le tutoriel.
Ajoutez un nouveau fichier de classe à votre projet qui dérive de HwndHost.
- Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet HelloComposition.
- Dans le menu contextuel, sélectionnez Ajouter>Classe....
- Dans la boîte de dialogue Ajouter un nouvel élément , nommez la classe CompositionHost.cs, puis cliquez sur Ajouter.
Dans CompositionHost.cs, modifiez la définition de classe à dériver de HwndHost.
// Add // using System.Windows.Interop; namespace HelloComposition { class CompositionHost : HwndHost { } }Ajoutez le code et le constructeur suivants à la classe.
// 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; }Remplacez les méthodes BuildWindowCore et DestroyWindowCore .
Note
Dans BuildWindowCore, vous appelez les méthodes InitializeCoreDispatcher et InitComposition . Vous créerez ces méthodes aux étapes suivantes.
// 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 et DestroyWindow nécessitent une déclaration PInvoke. Placez cette déclaration à la fin du code de la classe.
#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 declarationsInitialisez un thread avec un CoreDispatcher. Le répartiteur principal est responsable du traitement des messages de fenêtre et de l’envoi d’événements pour les API WinRT. Les nouvelles instances de CoreDispatcher doivent être créées sur un thread qui a un CoreDispatcher.
- Créez une méthode nommée InitializeCoreDispatcher et ajoutez du code pour configurer la file d’attente du répartiteur.
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; }- La file d’attente du répartiteur nécessite également une déclaration PInvoke. Placez cette déclaration dans la région de déclarations PInvoke que vous avez créée à l’étape précédente.
//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);Vous disposez maintenant de la file d’attente du répartiteur et pouvez commencer à initialiser et à créer du contenu de Composition.
Initialisez le compositeur. Le Compositor est une fabrique qui crée une variété de types dans le namespace Windows.UI.Composition, couvrant les visuels, le système des effets et le système d'animation. La classe Compositor gère également le cycle de vie des objets créés par l'usine.
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 et ICompositionTarget nécessitent des importations COM. Placez ce code après la classe CompositionHost , mais à l’intérieur de la déclaration d’espace de noms.
#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
Créer un UserControl pour ajouter votre contenu à l’arborescence visuelle WPF
La dernière étape pour configurer l’infrastructure requise pour héberger le contenu composition consiste à ajouter HwndHost à l’arborescence visuelle WPF.
Créer un UserControl
Un UserControl est un moyen pratique de empaqueter votre code qui crée et gère le contenu de composition, et d’ajouter facilement le contenu à votre code XAML.
Ajoutez un nouveau fichier de contrôle utilisateur à votre projet.
- Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet HelloComposition.
- Dans le menu contextuel, sélectionnez Ajouter un>contrôle utilisateur....
- Dans la boîte de dialogue Ajouter un nouvel élément , nommez le contrôle utilisateur CompositionHostControl.xaml, puis cliquez sur Ajouter.
Les fichiers CompositionHostControl.xaml et CompositionHostControl.xaml.cs sont créés et ajoutés à votre projet.
Dans CompositionHostControl.xaml, remplacez les
<Grid> </Grid>balises par cet élément Border , qui est le conteneur XAML dans lequel votre HwndHost entrera.<Border Name="CompositionHostElement"/>
Dans le code du contrôle utilisateur, vous créez une instance de la classe CompositionHost que vous avez créée à l’étape précédente et ajoutez-la en tant qu’élément enfant de CompositionHostElement, la bordure que vous avez créée dans la page XAML.
Dans CompositionHostControl.xaml.cs, ajoutez des variables privées pour les objets que vous utiliserez dans votre code composition. Ajoutez-les après la définition de classe.
CompositionHost compositionHost; Compositor compositor; Windows.UI.Composition.ContainerVisual containerVisual; DpiScale currentDpi;Ajoutez un gestionnaire pour l’événement Loaded du contrôle utilisateur. C’est là que vous configurez votre instance CompositionHost.
- Dans le constructeur, raccordez le gestionnaire d’événements comme indiqué ici (
Loaded += CompositionHostControl_Loaded;).
public CompositionHostControl() { InitializeComponent(); Loaded += CompositionHostControl_Loaded; }- Ajoutez la méthode de gestionnaire d’événements avec le nom 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(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost; compositor = compositionHost.Compositor; containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual; } }Dans cette méthode, vous configurez les objets que vous utiliserez dans votre code composition. Voici un aperçu rapide de ce qui se passe.
- Tout d’abord, vérifiez que la configuration n’est effectuée qu’une seule fois en vérifiant si une instance de CompositionHost existe déjà.
// 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) { }- Obtenez le DPI actuel. Cela est utilisé pour mettre à l’échelle correctement vos éléments de composition.
currentDpi = VisualTreeHelper.GetDpi(this);- Créez une instance de CompositionHost et affectez-la comme enfant de Border, CompositionHostElement.
compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost;- Obtenez le Compositor à partir de CompositionHost.
compositor = compositionHost.Compositor;- Utilisez le compositeur pour créer un visuel conteneur. Il s’agit du conteneur de composition auquel vous ajoutez vos éléments de composition.
containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual;- Dans le constructeur, raccordez le gestionnaire d’événements comme indiqué ici (
Ajouter des éléments de composition
Une fois l’infrastructure en place, vous pouvez générer le contenu de composition que vous souhaitez montrer.
Pour cet exemple, vous ajoutez du code qui crée et anime un simple spriteVisual carré.
Ajoutez un élément de composition. Dans CompositionHostControl.xaml.cs, ajoutez ces méthodes à la classe CompositionHostControl.
// 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); }
Gérer les modifications DPI
Le code pour ajouter et pour animer un élément prend en compte l’échelle actuelle des ppp lors de la création des éléments, mais vous devez également tenir compte des modifications des ppp pendant l’exécution de l’application. Vous pouvez gérer l’événement HwndHost.DpiChanged pour être informé des modifications et ajuster vos calculs en fonction du nouvel ppp.
Dans la méthode CompositionHostControl_Loaded, après la dernière ligne, ajoutez-la pour raccorder le gestionnaire d’événements DpiChanged.
compositionHost.DpiChanged += CompositionHost_DpiChanged;Ajoutez la méthode de gestionnaire d’événements avec le nom CompositionHostDpiChanged. Ce code ajuste l’échelle et le décalage de chaque élément et recalcule toutes les animations qui ne sont pas terminées.
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); } }
Ajouter le contrôle utilisateur à votre page XAML
Vous pouvez maintenant ajouter le contrôle utilisateur à votre interface utilisateur XAML.
Dans MainWindow.xaml, définissez la hauteur de la fenêtre sur 600 et la largeur sur 840.
Ajoutez le code XAML pour l’interface utilisateur. Dans MainWindow.xaml, ajoutez ce code XAML entre les balises racines
<Grid> </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"/>Gérer le clic sur le bouton pour créer de nouveaux éléments. (L’événement Click est déjà connecté au code XAML.)
Dans MainWindow.xaml.cs, ajoutez cette méthode de gestionnaire d’événements Button_Click . Ce code appelle CompositionHost.AddElement pour créer un élément avec une taille et un décalage générés de manière aléatoire.
// 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); }
Vous pouvez maintenant générer et exécuter votre application WPF. Si nécessaire, reportez-vous au code complet fourni à la fin du tutoriel pour vous assurer que tout le code se trouve à la bonne place.
Quand vous exécutez l’application et cliquez sur le bouton, vous devez voir les carrés animés ajoutés à l’interface utilisateur.
Étapes suivantes
Pour obtenir un exemple plus complet qui s’appuie sur la même infrastructure, consultez l’exemple d’intégration de couche visuelle WPF sur GitHub.
Ressources additionnelles
- Getting Started (WPF) (.NET)
- Interopérabilité avec du code non géré (.NET)
- Bien démarrer avec les applications Windows (UWP)
- Améliorer votre application de bureau pour Windows (UWP)
- Espace de noms Windows.UI.Composition (UWP)
Code complet
Voici le code complet de ce didacticiel.
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
}