Disegnare forme

Informazioni su come disegnare forme come ellissi, rettangoli, poligoni e percorsi. La classe Path permette di visualizzare un linguaggio di disegno basato su vettori abbastanza complesso in un'interfaccia utente XAML, ad esempio per disegnare curve di Bézier.

Due set di classi definiscono un'area dello spazio nell'interfaccia utente XAML: le classi Shape e le classi Geometry. La differenza principale tra queste classi è data dal fatto che a una classe Shape è associato un pennello di cui è possibile eseguire il rendering sullo schermo, mentre una classe Geometry definisce semplicemente un'area dello spazio e viene sottoposta a rendering solo se contribuisce a fornire informazioni a un'altra proprietà dell'interfaccia utente. Pensare a un oggetto Shape come a un UIElement con limiti definiti da un oggetto Geometry. Questo argomento illustra le classi Shape.

Le classi Shape sono Line, Ellipse, Rectangle, Polygon, Polyline e Path. Path è interessante perché definisce una geometria arbitraria, mentre la classe Geometry rappresenta un modo per definire le parti di un oggetto Path.

Piattaforma UWP e WinUI 2

Importante

Le informazioni e gli esempi in questo articolo sono ottimizzati per le app che usano Windows App SDK e WinUI 3, ma sono generalmente applicabili alle app UWP che usano WinUI 2. Per informazioni ed esempi specifici della piattaforma, consultare le indicazioni di riferimento sulle API UWP.

Questa sezione contiene informazioni necessarie per usare il controllo in un'app UWP o WinUI 2.

Le API per queste forme sono contenute nello spazio dei nomi Windows.UI.Xaml.Shapes.

Tratto e riempimento per le forme

Per eseguire il rendering di un elemento Shape nell'area di disegno dell'app, è necessario associare a esso un elemento Brush. Impostare la proprietà Fill dell'elemento Shape all'elemento Brush desiderato. Per altre informazioni sui pennelli, vedere Uso dei pennelli.

Un elemento Shape può anche avere un elemento Stroke, ovvero una linea disegnata intorno al perimetro della forma. Un elemento Stroke richiede anche un elemento Brush che ne definisce l'aspetto e un valore diverso da zero per StrokeThickness. StrokeThickness è una proprietà che definisce lo spessore del perimetro intorno al bordo della forma. Se non si specifica un valore Brush per Stroke o se si imposta StrokeThickness su 0, il bordo intorno alla forma non viene disegnato.

Ellisse

Un oggetto Ellipse è una forma con un perimetro curvo. Per creare un oggetto Ellipse di base, specificare un valore Width, Height e Brush per Fill.

L'esempio successivo crea un oggetto Ellipse con Width impostato su 200 e Height su 200 e usa un elemento SteelBlue di colore SolidColorBrush per Fill.

<Ellipse Fill="SteelBlue" Height="200" Width="200" />
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Colors.SteelBlue);
ellipse1.Width = 200;
ellipse1.Height = 200;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(ellipse1);

Ecco l'oggetto Ellipse dopo il rendering.

A rendered Ellipse.

In questo caso, l'oggetto Ellipse è una forma che la maggior parte delle persone definirebbe un cerchio, ma ecco come si dichiara un cerchio in XAML: si usa un oggetto Ellipse con gli stessi valori per Width e Height.

Quando si posiziona un oggetto Ellipse nel layout di un'interfaccia utente, si presume che le sue dimensioni siano uguali a quelle di un rettangolo con Width e Height. L'area esterna al perimetro non contiene il rendering ma fa comunque parte delle dimensioni dello slot del layout.

Un set di 6 oggetti Ellipse fa parte del modello per il controllo ProgressRing, mentre 2 oggetti Ellipse concentrici fanno parte di un oggetto RadioButton.

Rectangle

Un oggetto Rectangle è una forma a quattro lati con i due lati opposti uguali. Per creare un oggetto Rectangle di base, specificare un valore Width, Height e Fill.

È possibile arrotondare gli angoli di un oggetto Rectangle. Per creare angoli arrotondati, specificare un valore per le proprietà RadiusX e RadiusY. Queste proprietà specificano l'asse x e l'asse y di un'ellisse che definisce la curva degli angoli. Il valore massimo consentito di RadiusX è il valore Width diviso per due e il valore massimo consentito di RadiusY è il valore Height diviso per due.

L'esempio successivo crea un oggetto Rectangle con Width impostato su 200 e Height impostato su 100. Usa un valore Blue di SolidColorBrush per Fill e Black di SolidColorBrush per Stroke. Impostare StrokeThickness su 3. Impostare la proprietà RadiusX su 50 e la proprietà RadiusY su 10 per ottenere gli angoli arrotondati dell'oggetto Rectangle.

<Rectangle Fill="Blue"
           Width="200"
           Height="100"
           Stroke="Black"
           StrokeThickness="3"
           RadiusX="50"
           RadiusY="10" />
var rectangle1 = new Rectangle();
rectangle1.Fill = new SolidColorBrush(Colors.Blue);
rectangle1.Width = 200;
rectangle1.Height = 100;
rectangle1.Stroke = new SolidColorBrush(Colors.Black);
rectangle1.StrokeThickness = 3;
rectangle1.RadiusX = 50;
rectangle1.RadiusY = 10;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(rectangle1);

Ecco l'oggetto Rectangle dopo il rendering.

A rendered Rectangle.

Esistono alcuni scenari per le definizioni dell'interfaccia utente in cui anziché usare un oggetto Rectangle, sarebbe più appropriato usare Border. Se si intende creare una forma rettangolare intorno ad altro contenuto, potrebbe essere preferibile usare Border perché può avere contenuto figlio e si ridimensiona automaticamente intorno al contenuto, quindi evita di dover usare dimensioni fisse per altezza e larghezza come richiederebbe Rectangle. Un oggetto Border permette anche di avere angoli arrotondati se si imposta la proprietà CornerRadius.

D'altra parte, un oggetto Rectangle è probabilmente una scelta migliore per la composizione del controllo. Una forma Rectangle è presente in molti modelli di controllo perché viene usata come parte "FocusVisual" per i controlli attivabili. Ogni volta che il controllo si trova in uno stato di visualizzazione "Evidenziato", questo rettangolo viene reso visibile, negli altri stati è nascosto.

Polygon

Un oggetto Polygon è una forma con un limite definito da un numero arbitrario di punti. Il limite viene creato collegando una linea da un punto a quello successivo, con l'ultimo punto collegato al primo. La proprietà Points definisce la raccolta di punti che compongono il limite. In XAML si definiscono i punti con un elenco con valori delimitati da virgole. Nel code-behind usare un oggetto PointCollection per definire i punti e aggiungere ogni singolo punto come valore Point alla raccolta.

Non è necessario dichiarare in modo esplicito i punti in modo che il punto iniziale e quello finale siano entrambi specificati con lo stesso valore Point. La logica di rendering per un oggetto Polygon presuppone che si stia definendo una forma chiusa e che il punto finale venga collegato in modo implicito al punto iniziale.

L'esempio successivo crea un oggetto Polygon con 4 punti impostati su (10,200), (60,140), (130,140) e (180,200). Usa un valore LightBlue di SolidColorBrush per Fill e nessun valore per Stroke in modo che non sia presente alcun contorno di perimetro.

<Polygon Fill="LightBlue"
         Points="10,200,60,140,130,140,180,200" />
var polygon1 = new Polygon();
polygon1.Fill = new SolidColorBrush(Colors.LightBlue);

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polygon1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polygon1);

Ecco l'oggetto Polygon dopo il rendering.

A rendered Polygon.

Suggerimento

Un valore Point viene spesso usato come un tipo in XAML per scenari diversi dalla dichiarazione dei vertici delle forme. Ad esempio, un oggetto Point fa parte dei dati degli eventi tocco affinché sia possibile sapere esattamente il punto in cui si è verificata l'azione tocco in uno spazio di coordinate. Per altre informazioni su Point e su come usarlo in XAML o nel codice, vedere l'argomento di riferimento API per Point.

A linee

Un oggetto Line è semplicemente una linea disegnata tra due punti nello spazio delle coordinate. Un oggetto Line ignora qualsiasi valore fornito per Fill, perché non ha spazio interno. Per un oggetto Line, assicurarsi di specificare valori per le proprietà Stroke e StrokeThickness, altrimenti non sarà possibile eseguire il rendering dell'oggetto Line.

Non usare i valori Point per specificare una forma Line, ma usare valori Double discreti per X1, Y1, X2 e Y2. Questo consente un markup minimo per linee orizzontali o verticali. Ad esempio, <Line Stroke="Red" X2="400"/> definisce una linea orizzontale lunga 400 pixel. Le altre proprietà X,Y sono 0 per impostazione predefinita, quindi in termini di punti questo XAML disegnerebbe una linea da (0,0) a (400,0). È quindi possibile usare TranslateTransform per spostare l'intero oggetto Line, se si vuole che inizi da un punto diverso da (0,0).

<Line Stroke="Red" X2="400"/>
var line1 = new Line();
line1.Stroke = new SolidColorBrush(Colors.Red);
line1.X2 = 400;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(line1);

Polyline

Un oggetto Polyline è simile a un oggetto Polygon in quanto il limite della forma è definito da un set di punti, ad eccezione dell'ultimo punto di un oggetto Polyline che non è collegato al primo.

Nota

È possibile avere in modo esplicito un punto iniziale e uno finale identici nel set di oggetti Points per l'oggetto Polyline, ma in quel caso si sarebbe potuto usare un oggetto Polygon.

Se si specifica un oggetto Fill per un oggetto Polyline, Fill dipinge lo spazio interno della forma, anche se i punti iniziale e finale del set di elementi Points per l'oggetto Polyline non si intersecano. Se non si specifica un oggetto Fill, l'oggetto Polyline è simile all'oggetto di cui si sarebbe eseguito il rendering se fossero stati presenti diversi oggetti Line singoli in cui i punti iniziale e finale di linee consecutive si intersecavano.

Come con l'oggetto Polygon, la proprietà Points definisce la raccolta dei punti che compongono il limite. In XAML si definiscono i punti con un elenco con valori delimitati da virgole. Nel code-behind usare un oggetto PointCollection per definire i punti e aggiungere ogni singolo punto come struttura Point alla raccolta.

Questo esempio crea un oggetto Polyline con quattro punti impostati su (10,200), (60,140), (130,140) e (180,200). Un oggetto Stroke è definito, ma non è Fill.

<Polyline Stroke="Black"
          StrokeThickness="4"
          Points="10,200,60,140,130,140,180,200" />
var polyline1 = new Polyline();
polyline1.Stroke = new SolidColorBrush(Colors.Black);
polyline1.StrokeThickness = 4;

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polyline1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polyline1);

Questo è l'oggetto Polyline dopo il rendering. Si noti che il primo e l'ultimo punto non sono connessi dal contorno Stroke poiché si trovano in un oggetto Polygon.

A rendered Polyline.

Percorso

Un oggetto Path è l'oggetto Shape più versatile perché è possibile usarlo per definire una geometria arbitraria. Ma questa versatilità comporta anche un certo livello di complessità. Ora si vedrà come creare un oggetto Path di base in XAML.

Definire la geometria di un percorso con la proprietà Data. Sono disponibili due tecniche per impostare Data:

  • È possibile impostare un valore stringa per Data in XAML. In questa forma, il valore Path.Data usa un formato di serializzazione per la grafica. In genere non si modifica il testo di questo valore in formato stringa dopo che è stato stabilito per la prima volta. Si usano invece strumenti di progettazione che consentono di lavorare in una metafora di progettazione o disegno su una superficie. Quando si salva o esporta l'output, si ottiene un file XAML o un frammento di stringa XAML con informazioni Path.Data.
  • È possibile impostare la proprietà Data su un singolo oggetto Geometry. Questa operazione può essere eseguita nel codice o in XAML. Questo oggetto Geometry singolo è in genere un oggetto GeometryGroup, che funge da contenitore di più definizioni di geometria in un singolo oggetto ai fini del modello di oggetto. Il motivo più comune per procedere con questa operazione è il desiderio di usare una o più curve e forme complesse definibili come valori Segments per PathFigure, ad esempio BezierSegment.

Questo esempio mostra un oggetto Path che potrebbe essere stato generato dall'uso di Blend per Visual Studio per produrre solo alcune forme vettoriali e salvare il risultato come XAML. L'oggetto Path totale è composto da un segmento di curva di Bézier e da un segmento di linea. L'obiettivo è fornire alcuni esempi di elementi esistenti nel formato di serializzazione Path.Data e di ciò che rappresentano i numeri.

Questo elemento Data inizia con il comando di spostamento, indicato dalla lettera "M", che stabilisce un punto di partenza assoluto per il percorso.

Il primo segmento è una curva di Bézier cubica che inizia in corrispondenza di (100,200) e termina in corrispondenza di (400,175), disegnata usando i due punti di controllo (100,25) e (400,350). Questo segmento è indicato dal comando "C" nella stringa dell'attributo Data.

Il secondo segmento inizia con un comando di linea orizzontale assoluto "H", che specifica una linea disegnata dal punto finale del percorso secondario precedente (400,175) a un nuovo punto finale (280,175). Poiché si tratta di un comando di linea orizzontale, il valore specificato è una coordinata x.

<Path Stroke="DarkGoldenRod" 
      StrokeThickness="3"
      Data="M 100,200 C 100,25 400,350 400,175 H 280" />

Ecco l'oggetto Path dopo il rendering.

Screenshot of a simple rendered path.

L'esempio seguente mostra l'uso dell'altra tecnica descritta: un oggetto GeometryGroup con PathGeometry. Questo esempio mostra alcuni di tipi di geometria che è possibile usare nell'ambito di PathGeometry: PathFigure e i vari elementi che possono essere segmenti in PathFigure.Segments.

<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
    <Path.Data>
        <GeometryGroup>
            <RectangleGeometry Rect="50,5 100,10" />
            <RectangleGeometry Rect="5,5 95,180" />
            <EllipseGeometry Center="100, 100" RadiusX="20" RadiusY="30"/>
            <RectangleGeometry Rect="50,175 100,10" />
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsClosed="true" StartPoint="50,50">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <BezierSegment Point1="75,300" Point2="125,100" Point3="150,50"/>
                                    <BezierSegment Point1="125,300" Point2="75,100"  Point3="50,50"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>
        </GeometryGroup>
    </Path.Data>
</Path>
var path1 = new Microsoft.UI.Xaml.Shapes.Path();
path1.Fill = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 204, 204, 255));
path1.Stroke = new SolidColorBrush(Colors.Black);
path1.StrokeThickness = 1;

var geometryGroup1 = new GeometryGroup();
var rectangleGeometry1 = new RectangleGeometry();
rectangleGeometry1.Rect = new Rect(50, 5, 100, 10);
var rectangleGeometry2 = new RectangleGeometry();
rectangleGeometry2.Rect = new Rect(5, 5, 95, 180);
geometryGroup1.Children.Add(rectangleGeometry1);
geometryGroup1.Children.Add(rectangleGeometry2);

var ellipseGeometry1 = new EllipseGeometry();
ellipseGeometry1.Center = new Point(100, 100);
ellipseGeometry1.RadiusX = 20;
ellipseGeometry1.RadiusY = 30;
geometryGroup1.Children.Add(ellipseGeometry1);

var pathGeometry1 = new PathGeometry();
var pathFigureCollection1 = new PathFigureCollection();
var pathFigure1 = new PathFigure();
pathFigure1.IsClosed = true;
pathFigure1.StartPoint = new Windows.Foundation.Point(50, 50);
pathFigureCollection1.Add(pathFigure1);
pathGeometry1.Figures = pathFigureCollection1;

var pathSegmentCollection1 = new PathSegmentCollection();
var pathSegment1 = new BezierSegment();
pathSegment1.Point1 = new Point(75, 300);
pathSegment1.Point2 = new Point(125, 100);
pathSegment1.Point3 = new Point(150, 50);
pathSegmentCollection1.Add(pathSegment1);

var pathSegment2 = new BezierSegment();
pathSegment2.Point1 = new Point(125, 300);
pathSegment2.Point2 = new Point(75, 100);
pathSegment2.Point3 = new Point(50, 50);
pathSegmentCollection1.Add(pathSegment2);
pathFigure1.Segments = pathSegmentCollection1;

geometryGroup1.Children.Add(pathGeometry1);
path1.Data = geometryGroup1;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot">
layoutRoot.Children.Add(path1);

Ecco l'oggetto Path dopo il rendering.

Screenshot of a complex rendered path.

L'uso di PathGeometry potrebbe essere più leggibile rispetto alla popolazione di una stringa Path.Data. D'altra parte, Path.Data usa una sintassi compatibile con le definizioni di percorsi immagine SVG (Scalable Vector Graphics), pertanto potrebbe essere utile per portare la grafica da SVG o come output da uno strumento come Blend.