Визуальный компонент композиции

Визуальные компоненты композиции составляют визуальную древовидную структуру, которую используют и на которую опираются другие функции API композиции. API позволяет разработчикам определить и создать один или несколько объектов класса Visual, каждый из которых представляет отдельный узел визуального дерева.

Визуальные элементы

Существует несколько типов визуальных элементов, составляющих структуру визуального дерева, а также базовый класс кисти с несколькими подклассами, влияющими на содержимое визуального элемента:

  • Visual — базовый объект, обладающий большей частью свойств, которые наследуют другие объекты класса Visual.
  • ContainerVisual — является производным от класса Visual и добавляет возможность создавать дочерние объекты.
    • SpriteVisual — является производным от ContainerVisual. Имеет возможность связать кисть, чтобы визуальный элемент отображал пиксели, включая изображения, эффекты или сплошной цвет.
    • LayerVisual — является производным от ContainerVisual. Дочерние элементы визуального элемента объединяются в один слой.
      (Представлено в Windows 10 версии 1607, пакет SDK 14393.)
    • ShapeVisual — является производным от ContainerVisual. Узел визуального дерева, который является корнем CompositionShape.
      (Представлено в Windows 10 версии 1803, пакет SDK 17134.)
    • RedirectVisual — является производным от ContainerVisual. Визуальный элемент получает свое содержимое из другого визуального элемента.
      (Представлено в Windows 10, версия 1809, пакет SDK 17763.)
    • SceneVisual — является производным от ContainerVisual. Визуальный элемент контейнера для узлов трехмерной сцены.
      (Представлено в Windows 10 версии 1903, пакет SDK 18362.)

Контент и эффекты можно применить к объекту SpriteVisuals при помощи CompositionBrush и его подклассам, в том числе CompositionColorBrush,CompositionSurfaceBrush и CompositionEffectBrush. Дополнительные сведения о кисть см. в статье Общие сведения о CompositionBrush.

Пример CompositionVisual

Здесь мы рассмотрим пример кода, демонстрирующий три различных визуальных типа, перечисленные выше. Хотя этот пример не охватывает такие понятия, как анимация или более сложные эффекты, он содержит стандартные блоки, используемые всеми этими системами. (Полный код примера приведен в конце статьи.)

В примере отображается несколько заполненных сплошным цветом квадратов, которые можно нажимать и перетаскивать по экрану. Если щелкнуть на квадрате, он переместится на передний план, повернется на 45 градусов и станет непрозрачным при перетаскивании.

Здесь демонстрируются основные принципы работы с API, в том числе следующие.

  • Создание компоновщика
  • Создание объекта SpriteVisual и CompositionColorBrush
  • Обрезка объекта класса Visual
  • Поворот объекта класса Visual
  • Настройка уровня прозрачности
  • Изменение положения visual в коллекции.

Создание компоновщика

Вы можете с легкостью создать объект Compositor и сохранить его в переменной для использования в качестве фабрики. В следующем фрагменте кода показано создание нового объекта Compositor.

_compositor = new Compositor();

Создание объектов SpriteVisual и ColorBrush

С помощью компоновщика Compositor можно при необходимости с легкостью создавать такие объекты, как SpriteVisual и CompositionColorBrush.

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

Это всего лишь несколько строчек кода, но они демонстрируют всю мощь концепции. Основой системы эффектов являются объекты SpriteVisual. SpriteVisual обеспечивает гибкость при использовании и комбинировании цветов, изображений и создаваемых эффектов. SpriteVisual — это один визуальный тип, который может заполнять двухd прямоугольник кистью, в данном случае сплошным цветом.

Обрезка объекта класса Visual

Compositor также можно использовать для обрезки объектов класса Visual. Ниже приведен фрагмент примера, в котором InsetClip используется для усечения каждой стороны визуального объекта.

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

Как и другие объекты в API, объект InsetClip позволяет анимировать свои свойства.

Поворот клипа

К объекту класса Visual можно применить поворот. Обратите внимание, что свойство RotationAngle поддерживает значения как в радианах, так и в градусах. По умолчанию используется радианы, но можно легко указать градусы, как показано в следующем фрагменте кода:

child.RotationAngleInDegrees = 45.0f;

Rotation (Поворот) — это лишь один из компонентов преобразования, которые предоставляет данный API для упрощения таких задач. Сюда также относятся Offset (Смещение), Scale (Масштабирование), Orientation (Ориентация), RotationAxis (Ось поворота) и 4x4 TransformMatrix (Матрица преобразования 4 x 4).

Настройка уровня прозрачности

Установка уровня прозрачности визуального объекта — это простая операция, использующая значения с плавающей точкой. Например, в этом примере начальный уровень прозрачности всех квадратов равен 0,8.

visual.Opacity = 0.8f;

Как и поворот, свойство Opacity может быть анимировано.

Изменение положения объекта класса Visual в коллекции

API композиции позволяет изменить положение объекта класса Visual в коллекции VisualCollection несколькими способами. Его можно разместить над другим объектом Visual с помощью InsertAbove, разместить ниже с помощью InsertBelow, переместить вверх с помощью InsertAtTop или вниз с помощью InsertAtBottom.

В этом примере объект класса Visual, на который нажимает пользователь, перемещается наверх.

parent.Children.InsertAtTop(_currentVisual);

Полный пример

В полном примере все описанные выше принципы используются совместно для построения и просмотра простого дерева объектов класса Visual для изменения уровня прозрачности без использования XAML, WWA или DirectX. В следующем примере показано, как создаются и добавляются дочерние объекты класса Visual и как меняются свойства.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Core;

namespace compositionvisual
{
    class VisualProperties : IFrameworkView
    {
        //------------------------------------------------------------------------------
        //
        // VisualProperties.Initialize
        //
        // This method is called during startup to associate the IFrameworkView with the
        // CoreApplicationView.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Initialize(CoreApplicationView view)
        {
            _view = view;
            _random = new Random();
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.SetWindow
        //
        // This method is called when the CoreApplication has created a new CoreWindow,
        // allowing the application to configure the window and start producing content
        // to display.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.SetWindow(CoreWindow window)
        {
            _window = window;
            InitNewComposition();
            _window.PointerPressed += OnPointerPressed;
            _window.PointerMoved += OnPointerMoved;
            _window.PointerReleased += OnPointerReleased;
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerPressed
        //
        // This method is called when the user touches the screen, taps it with a stylus
        // or clicks the mouse.
        //
        //------------------------------------------------------------------------------

        void OnPointerPressed(CoreWindow window, PointerEventArgs args)
        {
            Point position = args.CurrentPoint.Position;

            //
            // Walk our list of visuals to determine who, if anybody, was selected
            //
            foreach (var child in _root.Children)
            {
                //
                // Did we hit this child?
                //
                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))
                {
                    //
                    // This child was hit. Since the children are stored back to front,
                    // the last one hit is the front-most one so it wins
                    //
                    _currentVisual = child as ContainerVisual;
                    _offsetBias = new Vector2((float)(offset.X - position.X),
                                              (float)(offset.Y - position.Y));
                }
            }

            //
            // If a visual was hit, bring it to the front of the Z order
            //
            if (_currentVisual != null)
            {
                ContainerVisual parent = _currentVisual.Parent as ContainerVisual;
                parent.Children.Remove(_currentVisual);
                parent.Children.InsertAtTop(_currentVisual);
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerMoved
        //
        // This method is called when the user moves their finger, stylus or mouse with
        // a button pressed over the screen.
        //
        //------------------------------------------------------------------------------

        void OnPointerMoved(CoreWindow window, PointerEventArgs args)
        {
            //
            // If a visual is selected, drag it with the pointer position and
            // make it opaque while we drag it
            //
            if (_currentVisual != null)
            {
                //
                // Set up the properties of the visual the first time it is
                // dragged. This will last for the duration of the drag
                //
                if (!_dragging)
                {
                    _currentVisual.Opacity = 1.0f;

                    //
                    // Transform the first child of the current visual so that
                    // the image is rotated
                    //
                    foreach (var child in _currentVisual.Children)
                    {
                        child.RotationAngleInDegrees = 45.0f;
                        child.CenterPoint = new Vector3(_currentVisual.Size.X / 2, _currentVisual.Size.Y / 2, 0);
                        break;
                    }

                    //
                    // Clip the visual to its original layout rect by using an inset
                    // clip with a one-pixel margin all around
                    //
                    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 = args.CurrentPoint.Position;
                _currentVisual.Offset = new Vector3((float)(position.X + _offsetBias.X),
                                                    (float)(position.Y + _offsetBias.Y),
                                                    0.0f);
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.OnPointerReleased
        //
        // This method is called when the user lifts their finger or stylus from the
        // screen, or lifts the mouse button.
        //
        //------------------------------------------------------------------------------

        void OnPointerReleased(CoreWindow window, PointerEventArgs args)
        {
            //
            // If a visual was selected, make it transparent again when it is
            // released and restore the transform and clip
            //
            if (_currentVisual != null)
            {
                if (_dragging)
                {
                    //
                    // Remove the transform from the first child
                    //
                    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;
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Load
        //
        // This method is called when a specific page is being loaded in the
        // application.  It is not used for this application.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Load(string unused)
        {

        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Run
        //
        // This method is called by CoreApplication.Run() to actually run the
        // dispatcher's message pump.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Run()
        {
            _window.Activate();
            _window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.Uninitialize
        //
        // This method is called during shutdown to disconnect the CoreApplicationView,
        // and CoreWindow from the IFrameworkView.
        //
        //------------------------------------------------------------------------------

        void IFrameworkView.Uninitialize()
        {
            _window = null;
            _view = null;
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.InitNewComposition
        //
        // This method is called by SetWindow(), where we initialize Composition after
        // the CoreWindow has been created.
        //
        //------------------------------------------------------------------------------

        void InitNewComposition()
        {
            //
            // Set up Windows.UI.Composition Compositor, root ContainerVisual, and associate with
            // the CoreWindow.
            //

            _compositor = new Compositor();

            _root = _compositor.CreateContainerVisual();



            _compositionTarget = _compositor.CreateTargetForCurrentView();
            _compositionTarget.Root = _root;

            //
            // Create a few visuals for our window
            //
            for (int index = 0; index < 20; index++)
            {
                _root.Children.InsertAtTop(CreateChildElement());
            }
        }

        //------------------------------------------------------------------------------
        //
        // VisualProperties.CreateChildElement
        //
        // Creates a small sub-tree to represent a visible element in our application.
        //
        //------------------------------------------------------------------------------

        Visual CreateChildElement()
        {
            //
            // Each element consists of three visuals, which produce the appearance
            // of a framed rectangle
            //
            var element = _compositor.CreateContainerVisual();
            element.Size = new Vector2(100.0f, 100.0f);

            //
            // Position this visual randomly within our window
            //
            element.Offset = new Vector3((float)(_random.NextDouble() * 400), (float)(_random.NextDouble() * 400), 0.0f);

            //
            // The outer rectangle is always white
            //
            //Note to preview API users - SpriteVisual and Color Brush replace SolidColorVisual
            //for example instead of doing
            //var visual = _compositor.CreateSolidColorVisual() and
            //visual.Color = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
            //we now use the below

            var visual = _compositor.CreateSpriteVisual();
            element.Children.InsertAtTop(visual);
            visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
            visual.Size = new Vector2(100.0f, 100.0f);

            //
            // The inner rectangle is inset from the outer by three pixels all around
            //
            var child = _compositor.CreateSpriteVisual();
            visual.Children.InsertAtTop(child);
            child.Offset = new Vector3(3.0f, 3.0f, 0.0f);
            child.Size = new Vector2(94.0f, 94.0f);

            //
            // Pick a random color for every rectangle
            //
            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)));
            child.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, red, green, blue));

            //
            // Make the subtree root visual partially transparent. This will cause each visual in the subtree
            // to render partially transparent, since a visual's opacity is multiplied with its parent's
            // opacity
            //
            element.Opacity = 0.8f;

            return element;
        }

        // CoreWindow / CoreApplicationView
        private CoreWindow _window;
        private CoreApplicationView _view;

        // Windows.UI.Composition
        private Compositor _compositor;
        private CompositionTarget _compositionTarget;
        private ContainerVisual _root;
        private ContainerVisual _currentVisual;
        private Vector2 _offsetBias;
        private bool _dragging;

        // Helpers
        private Random _random;
    }


    public sealed class VisualPropertiesFactory : IFrameworkViewSource
    {
        //------------------------------------------------------------------------------
        //
        // VisualPropertiesFactory.CreateView
        //
        // This method is called by CoreApplication to provide a new IFrameworkView for
        // a CoreWindow that is being created.
        //
        //------------------------------------------------------------------------------

        IFrameworkView IFrameworkViewSource.CreateView()
        {
            return new VisualProperties();
        }


        //------------------------------------------------------------------------------
        //
        // main
        //
        //------------------------------------------------------------------------------

        static int Main(string[] args)
        {
            CoreApplication.Run(new VisualPropertiesFactory());

            return 0;
        }
    }
}