Delen via


Samenstelling visualisaties

Samenstellingsvisuals vormen de visuele boomstructuur die alle andere functies van de Microsoft.UI.Composition-API gebruiken en waarop ze voortbouwen. Met de API kunnen ontwikkelaars een of meer visuele objecten definiëren en maken, die elk één knooppunt in een visualstructuur vertegenwoordigen.

Visuele elementen

Er zijn verschillende visuele typen die de visuele boomstructuur vormen, plus een basis penseelklasse met meerdere subklassen die van invloed zijn op de inhoud van een visual.

U kunt inhoud en effecten toepassen op SpriteVisuals met behulp van de CompositionBrush en de bijbehorende subklassen, waaronder de CompositionColorBrush, CompositionSurfaceBrush en CompositionEffectBrush. Zie Overzicht van CompositionBrush voor meer informatie over borstels.

Het CompositionVisual Sample

Hier bekijken we enkele WinUI 3-voorbeeldcode die de drie verschillende visualtypen laat zien die eerder zijn vermeld. Hoewel dit voorbeeld niet betrekking heeft op concepten zoals animaties of complexere effecten, bevat het de bouwstenen die al deze systemen gebruiken. (De volledige voorbeeldcode wordt aan het einde van dit artikel vermeld.)

In het voorbeeld ziet u een aantal effen kleur-kwadraten waarop kan worden geklikt en over het scherm kan worden gesleept. Wanneer er op een vierkant wordt geklikt, komt het naar voren, draait 45 graden en wordt het ondoorzichtig wanneer het wordt versleept.

Hier ziet u een aantal basisconcepten voor het werken met de API, waaronder:

  • Een compositor maken
  • Een SpriteVisual maken met een CompositionColorBrush
  • Een afbeelding uitsnijden
  • De visuele weergave roteren
  • Dekking instellen
  • De positie van het visuele element in de verzameling wijzigen.

Een compositor maken

Het maken van een compositor en het opslaan ervan in een variabele voor gebruik als fabriek is een eenvoudige taak. In een WinUI-app haalt u doorgaans de compositor op uit een XAML-element dat al verbonden is met de visuele boom.

Compositor compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;

Als u een compositor nodig hebt en geen UIElement beschikbaar hebt, kunt u in plaats daarvan gebruiken CompositionTarget.GetCompositorForCurrentThread() .

Een SpriteVisual en ColorBrush maken

Met behulp van compositor kunt u eenvoudig objecten maken wanneer u ze nodig hebt, zoals een SpriteVisual en een CompositionColorBrush:

var visual = _compositor.CreateSpriteVisual();
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));

Hoewel dit slechts een paar regels code is, demonstreert het een krachtig concept: SpriteVisual-objecten vormen het hart van het effectensysteem. De SpriteVisual biedt veel flexibiliteit en interactie in kleur, afbeelding en het creëren van effecten. SpriteVisual is één visueel type dat een 2D-rechthoek kan vullen met een kwast, in dit geval een effen kleur.

Een visuele component knippen

De compositor kan ook worden gebruikt om clips te maken voor een visual. Hieronder ziet u een voorbeeld van het voorbeeld van het gebruik van de InsetClip om elke kant van de visual te knippen:

var clip = _compositor.CreateInsetClip();
clip.LeftInset = 1.0f;
clip.RightInset = 1.0f;
clip.TopInset = 1.0f;
clip.BottomInset = 1.0f;
_currentVisual.Clip = clip;

Net als andere objecten in de API kan InsetClip animaties toepassen op de eigenschappen ervan.

Een clip draaien

Een visuele weergave kan worden getransformeerd met een draaiing. Houd er rekening mee dat RotationAngle zowel radialen als graden ondersteunt. Het is standaard ingesteld op radialen, maar het is eenvoudig om graden op te geven, zoals wordt weergegeven in het volgende fragment:

child.RotationAngleInDegrees = 45.0f;

Rotatie is slechts één voorbeeld van een set transformatieonderdelen die door de API worden geleverd om deze taken eenvoudiger te maken. Andere omvatten Offset, Schaal, Afdrukstand, RotationAxis en 4x4 TransformMatrix.

Dekking instellen

Het instellen van de opacity van een visual is een eenvoudige handeling met behulp van een float-waarde. In het voorbeeld beginnen alle vierkantjes bijvoorbeeld bij een dekking van 0,8:

visual.Opacity = 0.8f;

Net als bij rotatie kan de eigenschap Ondoorzichtigheid worden geanimeerd.

De positie van de visual in de verzameling wijzigen

Met de Samenstellings-API kan de positie van een visual in een VisualCollection op verschillende manieren worden gewijzigd. Het kan boven een andere visual worden geplaatst met InsertAbove, hieronder geplaatst met InsertBelow, verplaatst naar de bovenkant met InsertAtTop of de onderkant met InsertAtBottom.

In het voorbeeld wordt een visual waarop is geklikt, naar boven gesorteerd.

parent.Children.InsertAtTop(_currentVisual);

Volledig voorbeeld

In het volledige WinUI-voorbeeld worden alle bovenstaande concepten samen gebruikt om een eenvoudige structuur van Visual-objecten te maken en te doorlopen om ondoorzichtigheid te wijzigen zonder aangepaste DirectX-rendering. Het voorbeeld maakt gebruik van een WinUI Grid voor aanwijzerinvoer en fungeert als host voor de samenstellingsstructuur met ElementCompositionPreview.

<Page
    x:Class="CompositionVisualSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid
        x:Name="MyHost"
        Background="Transparent"
        PointerPressed="OnPointerPressed"
        PointerMoved="OnPointerMoved"
        PointerReleased="OnPointerReleased" />
</Page>
using System;
using System.Numerics;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Input;
using Windows.Foundation;
using Windows.UI;

namespace CompositionVisualSample
{
    public sealed partial class MainPage : Page
    {
        private readonly Random _random = new();
        private Compositor _compositor;
        private ContainerVisual _root;
        private ContainerVisual _currentVisual;
        private Vector2 _offsetBias;
        private bool _dragging;

        public MainPage()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (_root != null)
            {
                return;
            }

            _compositor = ElementCompositionPreview.GetElementVisual(MyHost).Compositor;
            _root = _compositor.CreateContainerVisual();
            ElementCompositionPreview.SetElementChildVisual(MyHost, _root);

            for (int index = 0; index < 20; index++)
            {
                _root.Children.InsertAtTop(CreateChildElement());
            }
        }

        private ContainerVisual CreateChildElement()
        {
            var element = _compositor.CreateContainerVisual();
            element.Size = new Vector2(100.0f, 100.0f);

            var maxX = (float)Math.Max(MyHost.ActualWidth - element.Size.X, 0.0);
            var maxY = (float)Math.Max(MyHost.ActualHeight - element.Size.Y, 0.0);
            element.Offset = new Vector3(
                (float)(_random.NextDouble() * maxX),
                (float)(_random.NextDouble() * maxY),
                0.0f);

            var outer = _compositor.CreateSpriteVisual();
            outer.Size = new Vector2(100.0f, 100.0f);
            outer.Brush = _compositor.CreateColorBrush(Colors.White);
            element.Children.InsertAtTop(outer);

            var inner = _compositor.CreateSpriteVisual();
            inner.Offset = new Vector3(3.0f, 3.0f, 0.0f);
            inner.Size = new Vector2(94.0f, 94.0f);

            byte red = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte green = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            byte blue = (byte)(0xFF * (0.2f + (_random.NextDouble() / 0.8f)));
            inner.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));
            outer.Children.InsertAtTop(inner);

            element.Opacity = 0.8f;
            return element;
        }

        private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
        {
            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual = null;

            foreach (var child in _root.Children)
            {
                Vector3 offset = child.Offset;
                Vector2 size = child.Size;

                if ((position.X >= offset.X) &&
                    (position.X < offset.X + size.X) &&
                    (position.Y >= offset.Y) &&
                    (position.Y < offset.Y + size.Y))
                {
                    _currentVisual = child as ContainerVisual;
                    _offsetBias = new Vector2(
                        (float)(offset.X - position.X),
                        (float)(offset.Y - position.Y));
                }
            }

            if (_currentVisual != null)
            {
                ContainerVisual parent = (ContainerVisual)_currentVisual.Parent;
                parent.Children.Remove(_currentVisual);
                parent.Children.InsertAtTop(_currentVisual);
            }
        }

        private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (!_dragging)
            {
                _currentVisual.Opacity = 1.0f;

                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngleInDegrees = 45.0f;
                    child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0.0f);
                    break;
                }

                var clip = _compositor.CreateInsetClip();
                clip.LeftInset = 1.0f;
                clip.RightInset = 1.0f;
                clip.TopInset = 1.0f;
                clip.BottomInset = 1.0f;
                _currentVisual.Clip = clip;

                _dragging = true;
            }

            Point position = e.GetCurrentPoint(MyHost).Position;
            _currentVisual.Offset = new Vector3(
                (float)(position.X + _offsetBias.X),
                (float)(position.Y + _offsetBias.Y),
                0.0f);
        }

        private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (_currentVisual == null)
            {
                return;
            }

            if (_dragging)
            {
                foreach (var child in _currentVisual.Children)
                {
                    child.RotationAngle = 0.0f;
                    child.CenterPoint = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                }

                _currentVisual.Opacity = 0.8f;
                _currentVisual.Clip = null;
                _dragging = false;
            }

            _currentVisual = null;
        }
    }
}

Als u een compositor moet initialiseren voordat u een XAML-hostelement hebt, gebruik dan CompositionTarget.GetCompositorForCurrentThread() en koppel vervolgens de visuele elementen aan uw gebruikersinterface wanneer er een hostelement beschikbaar is.