Dela via


Använda det visuella lagret med WPF

Du kan använda API:er för Windows Runtime-sammansättning (kallas även visuellt lager) i dina WPF-appar (Windows Presentation Foundation) för att skapa moderna upplevelser som lyser upp för Windows-användare.

Den fullständiga koden för den här självstudien finns på GitHub: WPF HelloComposition-exempel.

Förutsättningar

UWP XAML-värd-API:et har dessa förutsättningar.

Så här använder du API:er för sammansättning i WPF

I den här självstudien skapar du ett enkelt WPF-appgränssnitt och lägger till animerade sammansättningselement i den. Både WPF- och Composition-komponenterna hålls enkla, men den interop-kod som visas är densamma oavsett komponenternas komplexitet. Den färdiga appen ser ut så här.

Användargränssnittet för den körande appen

Skapa ett WPF-projekt

Det första steget är att skapa WPF-appprojektet, som innehåller en programdefinition och XAML-sidan för användargränssnittet.

Så här skapar du ett nytt WPF-programprojekt i Visual C# med namnet HelloComposition:

  1. Öppna Visual Studio och välj File>New>Project.

    Dialogrutan Nytt projekt öppnas.

  2. Under kategorin Installerad expanderar du noden Visual C# och väljer sedan Windows Desktop.

  3. Välj mallen WPF App (.NET Framework).

  4. Ange namnet HelloComposition, välj Framework .NET Framework 4.7.2 och klicka sedan på OK.

    Visual Studio skapar projektet och öppnar designern för standardprogramfönstret med namnet MainWindow.xaml.

Konfigurera projektet för att använda Windows Runtime-API:er

Om du vill använda Windows Runtime-API:er (WinRT) i din WPF-app måste du konfigurera Visual Studio-projektet för att få åtkomst till Windows Runtime. Dessutom används vektorer i stor utsträckning av API:er för sammansättning, så du måste lägga till de referenser som krävs för att använda vektorer.

NuGet-paket är tillgängliga för att hantera båda dessa behov. Installera de senaste versionerna av dessa paket för att lägga till nödvändiga referenser till projektet.

Anmärkning

Vi rekommenderar att du använder NuGet-paketen för att konfigurera projektet, men du kan lägga till de nödvändiga referenserna manuellt. Mer information finns i Förbättra ditt skrivbordsprogram för Windows. I följande tabell visas de filer som du behöver lägga till referenser till.

Fil Plats
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)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.2

Konfigurera projektet så att det är per bildskärm DPI-anpassat

Det visuella lagerinnehållet som du lägger till i din app skalas inte automatiskt för att matcha DPI-inställningarna för skärmen som visas på. Du måste aktivera per-skärm DPI-medvetenhet för din app och sedan se till att koden du använder för att skapa ditt visuella innehåll tar hänsyn till den aktuella DPI-skalan när appen körs. Här konfigurerar vi projektet så att det är DPI-medvetet. I senare avsnitt visar vi hur du använder DPI-informationen för att skala innehållet i det visuella lagret.

WPF-appar är system-DPI-medvetna som standard, men måste deklarera sig själva för att vara DPI-medvetna per övervakare i en app.manifest-fil. Så här aktiverar du DPI-medvetenhet på Windows-nivå per övervakare i appens manifestfil:

  1. Högerklicka på HelloComposition-projektet i Solution Explorer.

  2. I snabbmenyn väljer du Lägg till>nytt objekt....

  3. I dialogrutan Lägg till nytt objekt väljer du "Programmanifestfil" och klickar sedan på Lägg till. (Du kan lämna standardnamnet.)

  4. Leta reda på den här xml-filen i filen app.manifest och ta bort kommentaren:

    <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
    
  5. Lägg till den här inställningen efter den inledande <windowsSettings> taggen:

          <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
    
  6. Du måste också ange inställningen DoNotScaleForDpiChanges i App.config-filen.

    Öppna App.Config och lägg till den här xml-koden i elementet <configuration> :

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

Anmärkning

AppContextSwitchOverrides kan bara anges en gång. Om ditt program redan har en inställning måste du semikolonavgränsa den här inställningen i attributet 'värde'.

(Mer information finns i utvecklarguiden för bildskärmsanpassad DPI och exempel på GitHub.)

Skapa en HwndHost-härledd klass som värd för sammansättningselement

För att vara värd för innehåll som du skapar med det visuella lagret måste du skapa en klass som härleds från HwndHost. Det är här du gör det mesta av konfigurationen för att hosta Composition API:er. I den här klassen använder du Platform Invocation Services (PInvoke) och COM Interop för att föra in api:er för sammansättning i din WPF-app. Mer information om PInvoke och COM Interop finns i Interoperera med ohanterad kod.

Tips/Råd

Om du behöver det kontrollerar du den fullständiga koden i slutet av självstudien för att se till att all kod finns på rätt platser när du går igenom självstudien.

  1. Lägg till en ny klassfil i projektet som härleds från HwndHost.

    • Högerklicka på HelloComposition-projektet i Solution Explorer.
    • I snabbmenyn väljer du Lägg till>klass....
    • I dialogrutan Lägg till nytt objekt namnger du klassen CompositionHost.cs och klickar sedan på Lägg till.
  2. I CompositionHost.cs redigerar du klassdefinitionen så att den härleds från HwndHost.

    // Add
    // using System.Windows.Interop;
    
    namespace HelloComposition
    {
        class CompositionHost : HwndHost
        {
        }
    }
    
  3. Lägg till följande kod och konstruktor i klassen.

    // 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. Åsidosätt metoderna BuildWindowCore och DestroyWindowCore .

    Anmärkning

    I BuildWindowCore anropar du metoderna InitializeCoreDispatcher och InitComposition . Du skapar dessa metoder i nästa steg.

    // 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);
    }
    
    #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. Initiera en tråd med en CoreDispatcher. Kärnutskickaren ansvarar för att bearbeta fönstermeddelanden och skicka händelser för WinRT-API:er. Nya instanser av CoreDispatcher måste skapas på en tråd som har en CoreDispatcher.

    • Skapa en metod med namnet InitializeCoreDispatcher och lägg till kod för att konfigurera dispatcher-kön.
    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;
    }
    
    • Dispatcher-kön kräver också en PInvoke-deklaration. Placera den här deklarationen i regionen PInvoke-deklarationer som du skapade i föregående steg.
    //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);
    

    Nu har du dispatcher-kön redo och kan börja initiera och skapa innehåll för komposition.

  6. Initiera Kompositören. Compositor är en fabrik som skapar en mängd olika typer i namnområdet Windows.UI.Composition som omfattar visuella objekt, effektsystemet och animeringssystemet. Klassen Compositor hanterar också livslängden för objekt som skapats från fabriken.

    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 och ICompositionTarget kräver COM-importer. Placera den här koden efter klassen CompositionHost , men i namnområdesdeklarationen.
    #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
    

Skapa en UserControl för att lägga till ditt innehåll i det visuella WPF-trädet

Det sista steget för att konfigurera infrastrukturen som krävs för att vara värd för sammansättningsinnehåll är att lägga till HwndHost i det visuella WPF-trädet.

Skapa en UserControl

En UserControl är ett bekvämt sätt att paketera din kod som skapar och hanterar sammansättningsinnehåll och enkelt lägger till innehållet i XAML.

  1. Lägg till en ny användarkontrollfil i projektet.

    • Högerklicka på HelloComposition-projektet i Solution Explorer.
    • I snabbmenyn väljer du Lägg till>Användarkontroll....
    • I dialogrutan Lägg till nytt objekt ger du användarkontrollen namnet CompositionHostControl.xaml och klickar sedan på Lägg till.

    Både CompositionHostControl.xaml- och CompositionHostControl.xaml.cs-filerna skapas och läggs till i projektet.

  2. I CompositionHostControl.xaml ersätter du taggarna <Grid> </Grid> med det här kantlinjeelementet , som är den XAML-container som HwndHost kommer att gå in i.

    <Border Name="CompositionHostElement"/>
    

I koden för användarkontrollen skapar du en instans av klassen CompositionHost som du skapade i föregående steg och lägger till den som ett underordnat element i CompositionHostElement, kantlinjen som du skapade på XAML-sidan.

  1. I CompositionHostControl.xaml.cs lägger du till privata variabler för de objekt som du ska använda i kompositionskoden. Lägg till dessa efter klassdefinitionen.

    CompositionHost compositionHost;
    Compositor compositor;
    Windows.UI.Composition.ContainerVisual containerVisual;
    DpiScale currentDpi;
    
  2. Lägg till en hanterare för användarkontrollens Laddad-händelse. Det är här du konfigurerar din CompositionHost-instans.

    • I konstruktorn ansluter du händelsehanteraren enligt nedan (Loaded += CompositionHostControl_Loaded;).
    public CompositionHostControl()
    {
        InitializeComponent();
        Loaded += CompositionHostControl_Loaded;
    }
    
    • Lägg till händelsehanterarmetoden med namnet 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;
        }
    }
    

    I den här metoden konfigurerar du de objekt som du ska använda i kompositionskoden. Här är en snabb titt på vad som händer.

    • Kontrollera först att konfigurationen bara görs en gång genom att kontrollera om en instans av CompositionHost redan finns.
    // 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)
    {
    
    }
    
    • Hämta aktuellt DPI-värde. Detta används för att skala dina sammansättningselement korrekt.
    currentDpi = VisualTreeHelper.GetDpi(this);
    
    • Skapa en instans av CompositionHost och tilldela den som barn till Border, CompositionHostElement.
    compositionHost =
        new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);
    ControlHostElement.Child = compositionHost;
    
    • Hämta Compositor från CompositionHost.
    compositor = compositionHost.Compositor;
    
    • Använd Compositor för att skapa ett visuellt containerobjekt. Det här är containern Sammansättning som du lägger till dina sammansättningselement i.
    containerVisual = compositor.CreateContainerVisual();
    compositionHost.Child = containerVisual;
    

Lägga till sammansättningselement

Med infrastrukturen på plats kan du nu generera det sammansättningsinnehåll som du vill visa.

I det här exemplet lägger du till kod som skapar och animerar en enkel kvadratisk SpriteVisual.

  1. Lägg till ett kompositionselement. I CompositionHostControl.xaml.cs lägger du till dessa metoder i klassen 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);
    }
    

Hantera DPI-ändringar

Koden för att lägga till och animera ett element tar hänsyn till den aktuella DPI-skalan när element skapas, men du måste också ta hänsyn till DPI-ändringar medan appen körs. Du kan hantera HwndHost.DpiChanged-händelsen för att bli informerad om ändringar och justera dina beräkningar baserat på den nya DPI.

  1. I metoden CompositionHostControl_Loaded lägger du till den efter den sista raden för att ansluta händelsehanteraren DpiChanged.

    compositionHost.DpiChanged += CompositionHost_DpiChanged;
    
  2. Lägg till händelsehanterarmetoden med namnet CompositionHostDpiChanged. Den här koden justerar skalan och förskjutningen för varje element och beräknar om alla animeringar som inte är slutförda.

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

Lägg till användarkontrollen på XAML-sidan

Nu kan du lägga till användarkontrollen i ditt XAML-användargränssnitt.

  1. I MainWindow.xaml anger du Fönsterhöjd till 600 och Bredd till 840.

  2. Lägg till XAML för användargränssnittet. I MainWindow.xaml lägger du till denna XAML mellan rottaggar <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"/>
    
  3. Hantera knappklicket för att skapa nya element. (Klicka-händelsen är redan ansluten i XAML.)

    Lägg till den här Button_Click händelsehanterarmetoden i MainWindow.xaml.cs. Den här koden anropar CompositionHost.AddElement för att skapa ett nytt element med en slumpmässigt genererad storlek och förskjutning.

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

Nu kan du skapa och köra DIN WPF-app. Vid behov kontrollerar du den fullständiga koden i slutet av handledningen för att se till att all kod finns på rätt ställen.

När du kör appen och klickar på knappen bör du se animerade rutor som lagts till i användargränssnittet.

Nästa steg

Ett mer komplett exempel som bygger på samma infrastruktur finns i WPF Visual Layer-integreringsexemplet på GitHub.

Ytterligare resurser

Fullständig kod

Här är den fullständiga koden för den här handledningen.

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
}