Share via


Uso del livello visivo con WPF

Puoi usare le API di composizione di Windows Runtime (note anche come API livello visivo) nelle app Windows Presentation Foundation (WPF) per creare esperienze moderne disponibili per gli utenti di Windows.

Il codice completo per questa esercitazione è disponibile in GitHub: campione WPF HelloComposition.

Prerequisiti

L'API di hosting XAML UWP presenta questi prerequisiti.

Come usare le API di composizione in WPF

In questa esercitazione creerai una semplice interfaccia utente di un'app WPF e aggiungerai elementi di composizione animati. Vengono usati componenti di WPF e di composizione semplici, ma il codice di interoperabilità mostrato è lo stesso indipendentemente dalla complessità dei componenti. L'app completata ha un aspetto simile al seguente.

The running app UI

Creare un progetto WPF

Il primo passaggio consiste nel creare il progetto dell'app WPF, che include una definizione di applicazione e la pagina XAML per l'interfaccia utente.

Per creare un nuovo progetto di un'applicazione WPF in Visual C# denominato HelloComposition:

  1. Apri Visual Studio e seleziona File>Nuovo>Progetto.

    Verrà visualizzata la finestra di dialogo Nuovo progetto.

  2. Nella categoria Installati espandi il nodo Visual C# e quindi seleziona Windows Desktop.

  3. Seleziona il modello App WPF (.NET Framework).

  4. Immetti il nome HelloComposition, seleziona il framework .NET Framework 4.7.2 e quindi fai clic su OK.

    Visual Studio creerà il progetto e aprirà la finestra di progettazione per la finestra dell'applicazione predefinita denominata MainWindow.xaml.

Configurare il progetto per l'uso delle API di Windows Runtime

Per usare le API di Windows Runtime (WinRT) in un'app WPF, devi configurare il progetto di Visual Studio per l'accesso a Windows Runtime. Poiché inoltre le API di composizione usano ampiamente i vettori, devi aggiungere i riferimenti necessari per l'uso dei vettori.

Sono disponibili pacchetti NuGet per soddisfare entrambe queste esigenze. Installa le versioni più recenti di questi pacchetti per aggiungere al progetto i riferimenti necessari.

Nota

Sebbene sia consigliabile usare i pacchetti NuGet per configurare il progetto, puoi aggiungere manualmente i riferimenti necessari. Per altre informazioni, vedi Migliorare un'applicazione desktop per Windows. La tabella seguente illustra i file a cui devi aggiungere i riferimenti.

file Location
System.Runtime.WindowsRuntime C:\Windows\Microsoft.NET\Framework\v4.0.30319
Windows.Foundation.UniversalApiContract.winmd C:\Programmi (x86)\Windows Kits\10\References<versione sdk>\Windows.Foundation.UniversalApiContract<versione>
Windows.Foundation.FoundationContract.winmd C:\Programmi (x86)\Windows Kits\10\References<versione sdk>\Windows.Foundation.FoundationContract<versione>
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:\Programmi (x86)\Reference Assemblies\Microsoft\FrameworkNETFramework\v4.7.2

Configurare il progetto in modo che sia sensibile ai valori DPI del monitor

Il contenuto del livello visivo aggiunto all'app non viene ridimensionato automaticamente in base alle impostazioni DPI dello schermo su cui viene visualizzato. Devi configurare l'app in modo che sia sensibile ai valori DPI del monitor e quindi assicurarti che il codice usato per creare il contenuto del livello visivo prenda in considerazione la scala DPI corrente durante l'esecuzione dell'app. In questa fase il progetto viene configurato in modo che sia sensibile ai valori DPI. Nelle sezioni successive viene illustrato come usare le informazioni DPI per ridimensionare il contenuto del livello visivo.

Per impostazione predefinita, le app WPF sono sensibili ai valori DPI del sistema, ma devono dichiararsi come sensibili ai valori DPI del monitor in un file app.manifest. Per applicare la configurazione sensibile ai valori DPI del monitor a livello di Windows nel file manifesto dell'app:

  1. In Esplora soluzioni fai clic con il pulsante destro del mouse sul progetto HelloComposition.

  2. Scegli Aggiungi>Nuovo elemento... dal menu di scelta rapida.

  3. Nella finestra di dialogo Aggiungi nuovo elemento seleziona "File manifesto applicazione" e quindi fai clic su Aggiungi. Puoi lasciare invariato il nome predefinito.

  4. Nel file app.manifest trova questo codice XML e rimuovi il commento:

    <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
    
  5. Aggiungi questa impostazione dopo il tag <windowsSettings> di apertura:

          <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
    
  6. Devi inoltre configurare l'impostazione DoNotScaleForDpiChanges nel file App.config.

    Apri App.Config e aggiungi questo codice XML nell'elemento <configuration>:

    <runtime>
      <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
    </runtime>
    

Nota

È possibile impostare AppContextSwitchOverrides solo una volta. Se nell'applicazione dispone è già impostato AppContextSwitchOverrides, devi delimitare con punto e virgola questa opzione all'interno dell'attributo value.

Per altre informazioni, vedi la guida e gli esempi per gli sviluppatori di monitor DPI su GitHub.

Creare una classe derivata da HwndHost per ospitare elementi di composizione

Per ospitare il contenuto creato con il livello visivo, devi creare una classe che deriva da HwndHost Qui viene eseguita la maggior parte della configurazione per l'hosting delle API di composizione. In questa classe usi PInvoke (Platform Invocation Services) e l'interoperabilità COM per portare le API di composizione nell'app WPF. Per altre informazioni su PInvoke e sull'interoperabilità COM, vedi Interoperabilità con codice non gestito.

Suggerimento

Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette man mano che procedi con l'esercitazione.

  1. Aggiungi al progetto un nuovo file di classe derivante da HwndHost.

    • In Esplora soluzioni fai clic con il pulsante destro del mouse sul progetto HelloComposition.
    • Scegli Aggiungi>Classe... dal menu di scelta rapida.
    • Nella finestra di dialogo Aggiungi nuovo elemento assegna il nome CompositionHost.cs alla classe e quindi fai clic su Aggiungi.
  2. In CompositionHost.cs modifica la definizione della classe in modo che derivi da HwndHost.

    // Add
    // using System.Windows.Interop;
    
    namespace HelloComposition
    {
        class CompositionHost : HwndHost
        {
        }
    }
    
  3. Aggiungi il codice e il costruttore seguenti alla 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;
    }
    
  4. Esegui l'override dei metodi BuildWindowCore e DestroyWindowCore.

    Nota

    In BuildWindowCore chiama i metodi InitializeCoreDispatcher e InitComposition. Questi metodi vengono creati nei passaggi successivi.

    // 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 e DestroyWindow richiedono una dichiarazione PInvoke. Posiziona questa dichiarazione alla fine del codice per 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 declarations
    
  5. Inizializza un thread con un CoreDispatcher. Il dispatcher di base è responsabile dell'elaborazione dei messaggi finestra e dell'invio di eventi per le API WinRT. Le nuove istanze di CoreDispatcher devono essere create in un thread che disponga di CoreDispatcher.

    • Crea un metodo denominato InitializeCoreDispatcher e aggiungi il codice per configurare la coda del dispatcher.
    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 coda del dispatcher richiede inoltre una dichiarazione PInvoke. Posiziona questa dichiarazione all'interno dell'area PInvoke declarations creata nel passaggio precedente.
    //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);
    

    La coda Dispatcher è ora pronta e puoi iniziare a inizializzare e creare contenuto di composizione.

  6. Inizializza Compositor. Si tratta di una factory che crea un'ampia gamma di tipi nello spazio dei nomi Windows.UI.Composition, dagli oggetti visivi, al sistema degli effetti e al sistema di animazione. La classe Compositor gestisce anche la durata degli oggetti creati a partire dalla factory.

    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 e ICompositionTarget richiedono importazioni COM. Posiziona questo codice dopo la classe CompositionHost, ma all'interno della dichiarazione dello spazio dei nomi.
    #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
    

Creare un elemento UserControl per aggiungere il contenuto alla struttura ad albero visuale di WPF

L'ultimo passaggio per la configurazione dell'infrastruttura necessaria per ospitare il contenuto di composizione consiste nell'aggiungere HwndHost alla struttura ad albero visuale di WPF.

Creare un elemento UserControl

Un elemento UserControl consente di creare un pacchetto del codice che crea e gestisce il contenuto di composizione e di aggiungere facilmente il contenuto al file XAML.

  1. Aggiungi un nuovo file di controllo utente al progetto.

    • In Esplora soluzioni fai clic con il pulsante destro del mouse sul progetto HelloComposition.
    • Scegli Aggiungi>Controllo utente... dal menu di scelta rapida.
    • Nella finestra di dialogo Aggiungi nuovo elemento assegna il nome CompositionHostControl.xaml al controllo utente e quindi fai clic su Aggiungi.

    Vengono creati e aggiunti al progetto i file CompositionHostControl.xaml e CompositionHostControl.xaml.cs.

  2. In CompositionHostControl.xaml sostituisci i tag <Grid> </Grid> con questo elemento Border, che è il contenitore XAML in cui verrà posizionato HwndHost.

    <Border Name="CompositionHostElement"/>
    

Nel codice per il controllo utente crea un'istanza della classe CompositionHost creata nel passaggio precedente e aggiungila come elemento figlio di CompositionHostElement, l'elemento Border creato nella pagina XAML.

  1. In CompositionHostControl.xaml.cs aggiungi variabili private per gli oggetti che verranno usati nel codice di composizione. Aggiungili dopo la definizione della classe.

    CompositionHost compositionHost;
    Compositor compositor;
    Windows.UI.Composition.ContainerVisual containerVisual;
    DpiScale currentDpi;
    
  2. Aggiungi un gestore per l'evento Loaded del controllo utente in cui configurare l'istanza di CompositionHost.

    • Nel costruttore associa il gestore dell'evento come illustrato qui (Loaded += CompositionHostControl_Loaded;).
    public CompositionHostControl()
    {
        InitializeComponent();
        Loaded += CompositionHostControl_Loaded;
    }
    
    • Aggiungi il metodo del gestore dell'evento con nome 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;
        }
    }
    

    In questo metodo configura gli oggetti che verranno usati nel codice di composizione. Ecco una rapida panoramica di quanto accade.

    • Assicurati prima di tutto che la configurazione venga eseguita una sola volta controllando se esiste già un'istanza di CompositionHost.
    // 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)
    {
    
    }
    
    • Ottieni i valori DPI correnti. Vengono usati per ridimensionare correttamente gli elementi di composizione.
    currentDpi = VisualTreeHelper.GetDpi(this);
    
    • Crea un'istanza di CompositionHost e assegnala come elemento figlio di Border, CompositionHostElement.
    compositionHost =
        new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);
    ControlHostElement.Child = compositionHost;
    
    • Ottieni la classe Compositor da CompositionHost.
    compositor = compositionHost.Compositor;
    
    • Usa la classe Compositor per creare un oggetto visivo contenitore. Si tratta del contenitore di composizione a cui aggiungi gli elementi di composizione.
    containerVisual = compositor.CreateContainerVisual();
    compositionHost.Child = containerVisual;
    

Aggiungere elementi di composizione

Dopo aver definito l'infrastruttura, puoi generare il contenuto di composizione che vuoi mostrare.

Per questo esempio, aggiungi codice per la creazione e l'animazione a un quadrato SpriteVisual semplice.

  1. Aggiungi un elemento di composizione. In CompositionHostControl.xaml.cs aggiungi questi metodi alla 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);
    }
    

Gestire le modifiche DPI

Il codice per l'aggiunta e l'animazione di un elemento tiene conto della scala DPI corrente quando vengono creati gli elementi, ma devi anche tenere conto dei cambiamenti dei valori DPI durante l'esecuzione dell'app. Puoi gestire l'evento HwndHost.DpiChanged in modo da ricevere una notifica delle modifiche e adattare i calcoli in base ai nuovi valori DPI.

  1. Nel metodo CompositionHostControl_Loaded aggiungi dopo l'ultima riga il codice seguente per associare il gestore dell'evento DpiChanged.

    compositionHost.DpiChanged += CompositionHost_DpiChanged;
    
  2. Aggiungi il metodo del gestore dell'evento con nome CompositionHostDpiChanged. Questo codice regola la scala e l'offset di ogni elemento e ricalcola le animazioni che non sono complete.

    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);
        }
    }
    

Aggiungere il controllo utente a una pagina XAML

A questo punto puoi aggiungere il controllo utente all'interfaccia utente XAML.

  1. In MainWindow.xaml imposta l'altezza (Height) della finestra su 600 e la larghezza (Width) su 840.

  2. Aggiungi il codice XAML per l'interfaccia utente. In MainWindow.xaml aggiungi questo codice XAML tra i tag <Grid> </Grid> radice.

    <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"/>
    
  3. Gestisci l'evento Click del pulsante per la creazione di nuovi elementi. L'evento Click è già associato nel codice XAML.

    In MainWindow.xaml.cs aggiungi questo metodo del gestore dell'evento Button_Click. Questo codice chiama CompositionHost.AddElement per creare un nuovo elemento con una dimensione e un offset generati in modo casuale.

    // 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);
    }
    

A questo punto puoi compilare ed eseguire l'app WPF. Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette.

Quando esegui l'app e fai clic sul pulsante, dovresti visualizzare quadrati animati aggiunti all'interfaccia utente.

Passaggi successivi

Per un esempio più completo basato sulla stessa infrastruttura, vedi l'esempio di integrazione del livello visivo di WPF su GitHub.

Risorse aggiuntive

Codice completo

Di seguito è riportato il codice completo per questa esercitazione.

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
}