Oggetto visivo di composizione
Gli oggetti visivi di composizione costituiscono la struttura ad albero visuale su cui vengono usate e compilate tutte le altre funzionalità dell'API di composizione. L'API consente agli sviluppatori di definire e creare uno o più oggetti visivi ognuno dei quali rappresenta un singolo nodo in una struttura ad albero visuale.
Elementi grafici
Esistono diversi tipi di oggetto visivo che costituiscono la struttura ad albero visuale più una classe pennello di base con più sottoclassi che influiscono sul contenuto di un oggetto visivo:
- Visual: oggetto di base, la maggior parte delle proprietà è qui ed è ereditata dagli altri oggetti Visual.
- ContainerVisual: deriva da Visual e aggiunge la possibilità di creare elementi figlio.
- SpriteVisual: deriva da ContainerVisual. Ha la possibilità di associare un pennello in modo che l'oggetto visivo possa eseguire il rendering di pixel, incluse immagini, effetti o un colore a tinta unita.
- LayerVisual: deriva da ContainerVisual. Gli elementi figlio dell'oggetto visivo vengono appiattiti in un singolo livello.
(Introdotto in Windows 10, versione 1607, SDK 14393.) - ShapeVisual: deriva da ContainerVisual. Nodo della struttura ad albero visuale che rappresenta la radice di un oggetto CompositionShape.
(Introdotto in Windows 10, versione 1803, SDK 17134.) - RedirectVisual: deriva da ContainerVisual. L'oggetto visivo ottiene il contenuto da un altro oggetto visivo.
(Introdotto in Windows 10, versione 1809, SDK 17763.) - ShapeVisual: deriva da ContainerVisual. Oggetto visivo contenitore per i nodi di una scena 3D.
(Introdotto in Windows 10, versione 1903, SDK 18362.)
È possibile applicare contenuto ed effetti a SpriteVisuals usando CompositionBrush e le relative sottoclassi, tra cui CompositionColorBrush, CompositionSurfaceBrush e CompositionEffectBrush. Per altre informazioni sui pennelli, vedere Cenni preliminari su CompositionBrush.
Esempio di CompositionVisual
In questo esempio verranno esaminati alcuni esempi di codice che illustrano i tre diversi tipi di oggetto visivo elencati in precedenza. Anche se questo esempio non tratta concetti come animazioni o effetti più complessi, contiene i blocchi predefiniti usati da tutti i sistemi. Il codice di esempio completo è elencato alla fine di questo articolo.
Nell'esempio sono presenti diversi quadrati a tinta unita su cui è possibile fare clic e trascinare sullo schermo. Quando si fa clic su un quadrato, si arriva alla parte anteriore, ruota 45 gradi e diventa opaco quando viene trascinato.
Vengono illustrati alcuni concetti di base per l'uso dell'API, tra cui:
- Creazione di un compositor
- Creazione di un oggetto SpriteVisual con CompositionColorBrush
- Ritaglio dell'oggetto visivo
- Rotazione dell'oggetto visivo
- Impostazione dell'opacità
- Modifica della posizione dell'oggetto visivo nella raccolta.
Creazione di un compositor
La creazione di un compositor e l'archiviazione in una variabile da usare come factory è un'attività semplice. Il frammento di codice seguente illustra la creazione di un nuovo compositor:
_compositor = new Compositor();
Creazione di SpriteVisual e ColorBrush
Con un compositor è facile creare oggetti ogni volta che sono necessari, ad esempio SpriteVisual e CompositionColorBrush:
var visual = _compositor.CreateSpriteVisual();
visual.Brush = _compositor.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
Anche se si tratta solo di poche righe di codice, dimostra un concetto potente: gli oggetti SpriteVisual sono il cuore del sistema degli effetti. SpriteVisual consente grande flessibilità e interazione nel colore, nell'immagine e nella creazione di effetti. SpriteVisual è un singolo tipo di oggetto visivo che può riempire un rettangolo 2D con un pennello, in questo caso un colore a tinta unita.
Ritaglio di un oggetto visivo
Il compositor può anche essere usato per creare clip in un oggetto visivo. Di seguito è riportato un esempio dell'uso di InsetClip per tagliare ogni lato dell'oggetto visivo:
var clip = _compositor.CreateInsetClip();
clip.LeftInset = 1.0f;
clip.RightInset = 1.0f;
clip.TopInset = 1.0f;
clip.BottomInset = 1.0f;
_currentVisual.Clip = clip;
Analogamente ad altri oggetti nell'API, InsetClip può avere animazioni applicate alle relative proprietà.
Rotazione di una clip
Un oggetto visivo può essere trasformato con una rotazione. Si noti che RotationAngle supporta sia radianti che gradi. Il valore predefinito è radianti, ma è facile specificare i gradi, come illustrato nel frammento di codice seguente:
child.RotationAngleInDegrees = 45.0f;
La rotazione è solo un esempio di un set di componenti di trasformazione forniti dall'API per semplificare queste attività. Altri includono Offset, Scale, Orientation, RotationAxis e TransformMatrix 4x4.
Impostazione dell'opacità
L'impostazione dell'opacità di un oggetto visivo è un'operazione semplice tramite un valore float. Ad esempio, nell'esempio tutti i quadrati iniziano con opacità 0.8:
visual.Opacity = 0.8f;
Analogamente alla rotazione, la proprietà Opacity può essere animata.
Modifica della posizione dell'oggetto visivo nella raccolta
L'API Di composizione consente di modificare la posizione di un oggetto visivo in un oggetto VisualCollection in diversi modi. Può essere posizionato sopra un altro oggetto visivo con InsertAbove, posizionato sotto con InsertBelow, spostato in alto con InsertAtTop o in basso con InsertAtBottom.
Nell'esempio, un oggetto visivo su cui è stato fatto clic viene ordinato in alto:
parent.Children.InsertAtTop(_currentVisual);
Esempio completo
Nell'esempio completo, tutti i concetti precedenti vengono usati insieme per costruire e camminare un albero semplice di oggetti visivi per modificare l'opacità senza usare XAML, WWA o DirectX. In questo esempio viene illustrato come vengono creati e aggiunti oggetti visivi figlio e come vengono modificate le proprietà.
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;
}
}
}