In questo tutorial esamineremo le capacità di WPF nel disegno delle figure geometriche bi-dimensionali.
La classe base di tutte le figure geometriche presenti in WPF è la Shape che deriva da FrameworkElement secondo il seguente diagramma delle classi:
Quindi, le figure geometriche sono elementi grafici a tutti gli effetti (come Button o TextBox) e questo fatto comporta dei vantaggi che non abbiamo, ad esempio, nell’ambito delle Windows Forms:
· Ogni figura è in grado di auto-disegnarsi sollevandoci dall’incombenza di disegnare noi, via codice, la figura in caso di aggiornamento o ridimensionamento;
· Come tutti gli elementi grafici, le figure geometriche possono essere inserite in un qualsiasi contenitore;
· Le figure geometriche supportano gli eventi come tutti gli altri controlli senza dover implementare particolari workaround per gestire l’interazione con l’utente.
La classe Shape espone le proprietà comuni a tutte le figure geometriche. Le più interessanti sono:
· Fill : permette di impostare l’oggetto Brush con cui riempire la nostra figura;
· Stroke : permette di impostare l’oggetto Brush per il disegno del bordo;
· StrokeTickness : imposta la larghezza del bordo. Quando WPF disegna una linea spessa un certo numero di pixel, metà di questi viene disegnata all’esterno della figura e metà all’interno;
· StrokeDashArray; StrokeDashOffeset e StrokeDashCap : sono tre proprietà che permettono di disegnare il bordo con una linea discontinua. Ad esempio il seguente XAML:
- <Line StrokeThickness="5" Stroke="Blue" X1="0" X2="400" Y1="25" Y2="25"
- StrokeDashCap="Flat" StrokeDashOffset="30" StrokeDashArray="2" />
disegna una riga tratteggiata di larghezza 5 pixel, colore blue e tratti ampi 10 pixel.
· StrokeLineJoin e StrokeMiterLimit : permettono definire gli angoli delle figure geometriche. Queste proprietà agiscono sui vertici dei poligoni e non hanno effetti su cerchi ed ellissi;
· Stretch : permette di definire il comportamento della forma geometrica rispetto al contenitore che la contiene;
· DefiningGeometry : permette di accedere all’oggetto di classe Geometry della figura geometrica;
· GeometryTransform : Permette di applicare delle trasformazioni lineari alla figura geometrica in modo da poter ottenere effetti tipo rotazione, allungamento e così via.
Rettangolo
La classe Rectangle ci consente di disegnare un rettangolo.
Possiamo definire, oltre alle proprietà comuni a tutte le figure geometriche, anche l’aspetto degli angoli del rettangolo stesso. Utilizzando le proprietà RadiusX e RadiusY possiamo impostare gli assi, rispettivamente x e y, delle ellissi utilizzate per disegnare gli angoli del rettangolo.
Per capire meglio il funzionamento utilizziamo il seguente XAML:
- <Window x:Class="Rectangle"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Rectangle" Height="350" Width="500">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="40" />
- <RowDefinition Height="40" />
- <RowDefinition Height="*" />
- </Grid.RowDefinitions>
- <StackPanel Orientation="Horizontal" Grid.Row="0" VerticalAlignment="Center">
- <Label>RadiusX =</Label>
- <TextBox Text="{Binding ElementName=SliderX, Path=Value}" Width="50" />
- <Slider Margin="5" Name="SliderX" Maximum="150" Minimum="0" Value="{Binding Path=RadiusX, ElementName=Rectangle}" Width="200"/>
- </StackPanel>
- <StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Center">
- <Label>RadiusY =</Label>
- <TextBox Text="{Binding ElementName=SliderY, Path=Value}" Width="50" />
- <Slider Margin="5" Name="SliderY" Maximum="150" Minimum="0" Value="{Binding Path=RadiusY, ElementName=Rectangle}" Width="200"/>
- </StackPanel>
- <Rectangle Grid.Row="2" Margin="10" Stroke="black" StrokeThickness="2" Name="Rectangle">
- <Rectangle.Fill>
- <RadialGradientBrush>
- <GradientStopCollection>
- <GradientStop Offset="0" Color="Green"></GradientStop>
- <GradientStop Offset="1" Color="GreenYellow"></GradientStop>
- </GradientStopCollection>
- </RadialGradientBrush>
- </Rectangle.Fill>
- </Rectangle>
- </Grid>
- </Window>
La finestra che ne deriva ci mette a disposizione due sliders per impostare le due proprietà per verificare il loro comportamento.
Nel precedente XAML possiamo anche vedere un esempio di come impostare le proprietà Fill utilizzando un RadialGradientBrush.
Ellisse
Per disegnare una ellisse (o un cerchio, nel caso in cui i due assi siano uguali) possiamo utilizzare la classe Ellipse. Possiamo gestire la grandezza degli assi dell’ellisse utilizzando le proprietà Height e Width.
Nel seguente XAML. vediamo come disegnare un’ellisse che occupa l’intero spazio del suo contenitore e come colorare la stessa utilizzando un LinearGradientBrush le cui proprietà possono essere modificate utilizzando gli sliders:
- <Window x:Class="Ellipse"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Ellipse" Height="300" Width="500">
- <Grid>
- <Grid.RowDefinitions >
- <RowDefinition Height="30" />
- <RowDefinition Height="30" />
- <RowDefinition Height="*" />
- </Grid.RowDefinitions>
- <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="2">
- <Label Content="StartPoint" Width="100"/>
- <Label Content="X"/>
- <Slider Minimum="0" Maximum="1" Width="150" Name="sliderStartPointX" ValueChanged="slider_ValueChanged" />
- <Label Content="Y"/>
- <Slider Minimum="0" Maximum="1" Width="150" Name="sliderStartPointY" ValueChanged="slider_ValueChanged" />
- </StackPanel>
- <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="2">
- <Label Content="EndPoint" Width="100"/>
- <Label Content="X"/>
- <Slider Minimum="0" Maximum="1" Width="150" Name="sliderEndPointX" ValueChanged="slider_ValueChanged" Value="1"/>
- <Label Content="Y"/>
- <Slider Minimum="0" Maximum="1" Width="150" Name="sliderEndPointY" ValueChanged="slider_ValueChanged" Value="1"/>
- </StackPanel>
- <Ellipse StrokeThickness="10" Margin="5" Grid.Row="2" Name="ellipse">
- <Ellipse.Stroke>
- <LinearGradientBrush >
- <GradientStop Color="Red" Offset="1" />
- <GradientStop Color="green" Offset="0.75" />
- <GradientStop Color="Yellow" Offset="0.5" />
- <GradientStop Color="Blue" Offset="0.25" />
- <GradientStop Color="White" Offset="0" />
- </LinearGradientBrush>
- </Ellipse.Stroke>
- <Ellipse.Fill>
- <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
- <GradientStopCollection >
- <GradientStop Color="Red" Offset="0" />
- <GradientStop Color="green" Offset="0.25" />
- <GradientStop Color="Yellow" Offset="0.5" />
- <GradientStop Color="Blue" Offset="0.75" />
- <GradientStop Color="White" Offset="1" />
- </GradientStopCollection>
- </LinearGradientBrush>
- </Ellipse.Fill>
- </Ellipse>
- </Grid>
- </Window>
Nel precedente esempio, la modifica del gradiente che colora l’ellisse viene eseguita nel code behind della finestra e, in particolare nel gestore dell’evento ValueChanged dello Slider:
- Private Sub slider_ValueChanged(ByVal sender As System.Object,
- ByVal e As System.Windows.RoutedPropertyChangedEventArgs(Of System.Double))
- If ellipse IsNot Nothing Then
- Dim brush = CType(ellipse.Fill, LinearGradientBrush)
- Dim startPoint = brush.StartPoint
- Dim endPoint = brush.EndPoint
- Select Case CType(sender, Control).Name
- Case "sliderStartPointX"
- startPoint.X = e.NewValue
- Case "sliderStartPointY"
- startPoint.Y = e.NewValue
- Case "sliderEndPointX"
- endPoint.X = e.NewValue
- Case "sliderEndPointY"
- endPoint.Y = e.NewValue
- End Select
- brush.StartPoint = startPoint
- brush.EndPoint = endPoint
- End If
- End Sub
Nel precedente codice recuperiamo il LinearGradientBrush con cui abbiamo colorato l’ellisse e modifichiamo il punto di inizio e di fine del segmento su cui sono disposti i vari Stop.
Linea
Disegnare linee in WPF è molto semplice, basta utilizzare la classe Line.
- <Line StrokeThickness="10" Stroke="black" X1="0" X2="400" Y1="20" Y2="20" />
Le proprietà x1, y1 e x2, y2 definiscono, rispettivamente, il punto di partenza e di arrivo della linea.
Nel seguente esempio disegniamo due linee di larghezza 10 pixel parallele tra loro:
- <Window x:Class="Line"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Line" Height="100" Width="400" SnapsToDevicePixels="True">
- <Grid>
- <Line StrokeThickness="10" Stroke="black" X1="0" X2="400" Y1="20" Y2="20" />
- <Line StrokeThickness="10" Stroke="black" X1="0" X2="400" Y1="40" Y2="40" />
- </Grid>
- </Window>
Polyline
La classe Polyline permette di disegnare una linea spezzata composta da tanti segmenti attaccati tra loro.
Un esempio di polyline è il seguente:
- <Polyline Stroke="red" StrokeThickness="5"
- Points="0,10 100,110 100,10 200,110 200,10 300,110 200,210 100,210">
- </Polyline>
La collezione di punti presente nella proprietà Points definisce, a coppie, i punti iniziali e finali della spezzata:
La collezione dei punti può essere ovviamente espressa come PointCollection:
- <Polyline Stroke="red" StrokeThickness="5"
- StrokeLineJoin="Bevel" >
- <Polyline.Points>
- <PointCollection>
- <Point X="10" Y="0"></Point>
- <Point X="110" Y="100"></Point>
- <Point X="210" Y="0"></Point>
- <Point X="310" Y="100"></Point>
- </PointCollection>
- </Polyline.Points>
- </Polyline>
Possiamo controllare il modo con cui i singoli segmenti si sovrappongono utilizzando la proprietà StrokeLineJoin:
Poligono
Se la classe PolyLine ci consente di disegnare una spezzata, la classe Polygon ci permette di tracciare un poligono. Il funzionamento della classe Polygon è molto simile a quello della classe Polyline, e come quest’ultima ha una proprietà che contiene un insieme di punti che definiscono i vertici del poligono. La classe Polygon congiunge automaticamente l’ultimo punto della collection Points con il primo per chiudere la figura geometrica.
- <Window x:Class="Polygon"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Polygon" Height="300" Width="450">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="200"></RowDefinition>
- <RowDefinition Height="*"></RowDefinition>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="*"></ColumnDefinition>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
- <Polygon Stroke="black"
- Points="50,50 100,0 200,100 100,200 0,100 50,50 150,50 150,150 50,150"
- Fill="green" FillRule="EvenOdd" HorizontalAlignment="Center" ></Polygon>
- <Polygon Stroke="black"
- Points="50,50 100,0 200,100 100,200 0,100 50,50 150,50 150,150 50,150"
- Fill="green" FillRule="Nonzero" Grid.Column="1" HorizontalAlignment="Center" ></Polygon>
- <TextBlock Grid.Row="1" HorizontalAlignment="Center"
- VerticalAlignment="Top" Margin="5" FontSize="20" >EvenOdd</TextBlock>
- <TextBlock Grid.Row="1" HorizontalAlignment="Center" Grid.Column="1"
- VerticalAlignment="Top" Margin="5" FontSize="20" >NonZero</TextBlock>
- </Grid>
- </Window>
La proprietà FillRule consente di scegliere il modo con cui la figura geometrica identifica la sua parte interna (per esempio per colorarla con il brush contenuto nella proprietà Fill) e la sua parte esterna.
I valori possibili per la proprietà FillRule sono i seguenti:
· EvenOdd : in questa modalità il framework individua un punto come interno alla figura se, disegnando virtualmente dei raggi a partire dal punto stesso in ogni direzione, il numero di segmenti della figura incrociati è dispari.
· NonZero : il meccanismo si basa sulla direzione dei segmenti disegnati. In pratica si procede così:
Si disegna un qualsiasi raggio a partire dal punto;
Si comincia il conteggio da zero;
Si aggiunge 1 ogni volta che il raggio incrocia un segmento della figura disegnato da sinistra a destra;
Si sottrae 1 ogni volta che il raggio incrocia un segmento della figura disegnato da destra a sinistra;
Il punto è interno se il conteggio è diverso da zero, altrimenti è esterno.
Le figure seguenti illustrano i due procedimenti:
Path
La classe Path permette di realizzare curve complesse componendo segmenti semplici che possono essere lineari, archi o curve di Bezier.
Un esempio di utilizzo è il seguente:
- <Window x:Class="Path"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Path" Height="350" Width="350">
- <Grid>
- <Path Stroke="Black" StrokeThickness="3">
- <Path.Data>
- <PathGeometry>
- <PathGeometry.Figures>
- <PathFigureCollection>
- <PathFigure StartPoint="10,100">
- <PathFigure.Segments>
- <PathSegmentCollection>
- <BezierSegment Point1="100,0" Point2="200,200" Point3="300,100" />
- <BezierSegment Point1="100,0" Point2="200,200" Point3="300,100" />
- <BezierSegment Point1="300,100" Point2="200,200" Point3="100,200" />
- <BezierSegment Point1="50,50" Point2="100,100" Point3="150,150" />
- </PathSegmentCollection>
- </PathFigure.Segments>
- </PathFigure>
- </PathFigureCollection>
- </PathGeometry.Figures>
- </PathGeometry>
- </Path.Data>
- </Path>
- </Grid>
- </Window>
Commenti