venerdì 19 marzo 2010

Appunti di WPF – Dodicesima Puntata – Le figure geometriche

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:

WPF_12_Shapes_Fig1

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:

  1. <Line StrokeThickness="5" Stroke="Blue" X1="0" X2="400" Y1="25" Y2="25"
  2.     StrokeDashCap="Flat" StrokeDashOffset="30" StrokeDashArray="2" />

disegna una riga tratteggiata di larghezza 5 pixel, colore blue e tratti ampi 10 pixel.

WPF_12_Shapes_Fig2 
· 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:

  1. <Window x:Class="Rectangle"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Rectangle" Height="350" Width="500">
  5.     <Grid>
  6.         <Grid.RowDefinitions>
  7.             <RowDefinition Height="40" />
  8.             <RowDefinition Height="40" />
  9.             <RowDefinition Height="*" />
  10.         </Grid.RowDefinitions>
  11.         <StackPanel Orientation="Horizontal" Grid.Row="0" VerticalAlignment="Center">
  12.             <Label>RadiusX =</Label>
  13.             <TextBox Text="{Binding ElementName=SliderX, Path=Value}" Width="50" />
  14.             <Slider Margin="5" Name="SliderX" Maximum="150" Minimum="0" Value="{Binding Path=RadiusX, ElementName=Rectangle}" Width="200"/>
  15.         </StackPanel>
  16.         <StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Center">
  17.             <Label>RadiusY =</Label>
  18.             <TextBox Text="{Binding ElementName=SliderY, Path=Value}" Width="50" />
  19.             <Slider Margin="5" Name="SliderY" Maximum="150" Minimum="0" Value="{Binding Path=RadiusY, ElementName=Rectangle}" Width="200"/>
  20.         </StackPanel>
  21.         <Rectangle Grid.Row="2" Margin="10" Stroke="black" StrokeThickness="2" Name="Rectangle">
  22.             <Rectangle.Fill>
  23.                 <RadialGradientBrush>
  24.                     <GradientStopCollection>
  25.                         <GradientStop Offset="0" Color="Green"></GradientStop>
  26.                         <GradientStop Offset="1" Color="GreenYellow"></GradientStop>
  27.                     </GradientStopCollection>
  28.                 </RadialGradientBrush>
  29.             </Rectangle.Fill>
  30.         </Rectangle>
  31.     </Grid>
  32. </Window>

La finestra che ne deriva ci mette a disposizione due sliders per impostare le due proprietà per verificare il loro comportamento.

WPF_12_Shapes_Fig3WPF_12_Shapes_Fig4

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:

  1. <Window x:Class="Ellipse"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Ellipse" Height="300" Width="500">
  5.     <Grid>
  6.         <Grid.RowDefinitions >
  7.             <RowDefinition Height="30" />
  8.             <RowDefinition Height="30" />
  9.             <RowDefinition Height="*" />
  10.         </Grid.RowDefinitions>
  11.         <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="2">
  12.             <Label Content="StartPoint" Width="100"/>
  13.             <Label Content="X"/>
  14.             <Slider Minimum="0" Maximum="1" Width="150" Name="sliderStartPointX" ValueChanged="slider_ValueChanged" />
  15.             <Label Content="Y"/>
  16.             <Slider Minimum="0" Maximum="1" Width="150" Name="sliderStartPointY" ValueChanged="slider_ValueChanged" />
  17.         </StackPanel>
  18.         <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="2">
  19.             <Label Content="EndPoint" Width="100"/>
  20.             <Label Content="X"/>
  21.             <Slider Minimum="0" Maximum="1" Width="150" Name="sliderEndPointX" ValueChanged="slider_ValueChanged" Value="1"/>
  22.             <Label Content="Y"/>
  23.             <Slider Minimum="0" Maximum="1" Width="150" Name="sliderEndPointY" ValueChanged="slider_ValueChanged" Value="1"/>
  24.         </StackPanel>
  25.         <Ellipse StrokeThickness="10" Margin="5" Grid.Row="2" Name="ellipse">
  26.             <Ellipse.Stroke>
  27.                 <LinearGradientBrush >
  28.                     <GradientStop Color="Red" Offset="1" />
  29.                     <GradientStop Color="green" Offset="0.75" />
  30.                     <GradientStop Color="Yellow" Offset="0.5" />
  31.                     <GradientStop Color="Blue" Offset="0.25" />
  32.                     <GradientStop Color="White" Offset="0" />
  33.                 </LinearGradientBrush>
  34.             </Ellipse.Stroke>
  35.             <Ellipse.Fill>
  36.                   <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
  37.                     <GradientStopCollection >
  38.                         <GradientStop Color="Red" Offset="0" />
  39.                         <GradientStop Color="green" Offset="0.25" />
  40.                         <GradientStop Color="Yellow" Offset="0.5" />
  41.                         <GradientStop Color="Blue" Offset="0.75" />
  42.                         <GradientStop Color="White" Offset="1" />
  43.                     </GradientStopCollection>
  44.                 </LinearGradientBrush>
  45.             </Ellipse.Fill>
  46.         </Ellipse>
  47.     </Grid>
  48.     </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:

  1. Private Sub slider_ValueChanged(ByVal sender As System.Object,
  2.                                 ByVal e As System.Windows.RoutedPropertyChangedEventArgs(Of System.Double))
  3.     If ellipse IsNot Nothing Then
  4.         Dim brush = CType(ellipse.Fill, LinearGradientBrush)
  5.         Dim startPoint = brush.StartPoint
  6.         Dim endPoint = brush.EndPoint
  7.         Select Case CType(sender, Control).Name
  8.             Case "sliderStartPointX"
  9.                 startPoint.X = e.NewValue
  10.             Case "sliderStartPointY"
  11.                 startPoint.Y = e.NewValue
  12.             Case "sliderEndPointX"
  13.                 endPoint.X = e.NewValue
  14.             Case "sliderEndPointY"
  15.                 endPoint.Y = e.NewValue
  16.         End Select
  17.         brush.StartPoint = startPoint
  18.         brush.EndPoint = endPoint
  19.     End If
  20. 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.

  1.  
  2. <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:

  1. <Window x:Class="Line"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Line" Height="100" Width="400" SnapsToDevicePixels="True">
  5.     <Grid>
  6.         <Line StrokeThickness="10" Stroke="black" X1="0" X2="400" Y1="20" Y2="20" />
  7.         <Line StrokeThickness="10" Stroke="black" X1="0" X2="400" Y1="40" Y2="40" />
  8.     </Grid>
  9. </Window>

WPF_12_Shapes_Fig5

Polyline

La classe Polyline permette di disegnare una linea spezzata composta da tanti segmenti attaccati tra loro.

Un esempio di polyline è il seguente:

  1. <Polyline Stroke="red" StrokeThickness="5"
  2. Points="0,10 100,110 100,10 200,110 200,10 300,110 200,210 100,210">
  3. </Polyline>

La collezione di punti presente nella proprietà Points definisce, a coppie, i punti iniziali e finali della spezzata:

WPF_12_Shapes_Fig6

La collezione dei punti può essere ovviamente espressa come PointCollection:

  1. <Polyline Stroke="red" StrokeThickness="5"
  2.             StrokeLineJoin="Bevel" >
  3.     <Polyline.Points>
  4.         <PointCollection>
  5.             <Point X="10" Y="0"></Point>
  6.             <Point X="110" Y="100"></Point>
  7.             <Point X="210" Y="0"></Point>
  8.             <Point X="310" Y="100"></Point>
  9.         </PointCollection>
  10.     </Polyline.Points>
  11. </Polyline>

Possiamo controllare il modo con cui i singoli segmenti si sovrappongono utilizzando la proprietà StrokeLineJoin:

WPF_12_Shapes_Fig7

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.

  1. <Window x:Class="Polygon"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Polygon" Height="300" Width="450">
  5.     <Grid>
  6.         <Grid.RowDefinitions>
  7.             <RowDefinition Height="200"></RowDefinition>
  8.                     <RowDefinition Height="*"></RowDefinition>
  9.         </Grid.RowDefinitions>
  10.         <Grid.ColumnDefinitions>
  11.             <ColumnDefinition Width="*"></ColumnDefinition>
  12.             <ColumnDefinition Width="*"></ColumnDefinition>
  13.         </Grid.ColumnDefinitions>
  14.         <Polygon Stroke="black"
  15.                  Points="50,50 100,0 200,100 100,200 0,100 50,50 150,50 150,150 50,150"
  16.                  Fill="green" FillRule="EvenOdd" HorizontalAlignment="Center" ></Polygon>
  17.         <Polygon Stroke="black"
  18.                  Points="50,50 100,0 200,100 100,200 0,100 50,50 150,50 150,150 50,150"
  19.                  Fill="green" FillRule="Nonzero" Grid.Column="1" HorizontalAlignment="Center" ></Polygon>
  20.         <TextBlock Grid.Row="1" HorizontalAlignment="Center"
  21.                    VerticalAlignment="Top" Margin="5" FontSize="20" >EvenOdd</TextBlock>
  22.         <TextBlock Grid.Row="1" HorizontalAlignment="Center" Grid.Column="1"
  23.                    VerticalAlignment="Top" Margin="5" FontSize="20" >NonZero</TextBlock>
  24.     </Grid>
  25. </Window>

WPF_12_Shapes_Fig8 
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:

WPF_12_Shapes_Fig9

WPF_12_Shapes_Fig10

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:

  1. <Window x:Class="Path"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Path" Height="350" Width="350">
  5.     <Grid>
  6.         <Path Stroke="Black" StrokeThickness="3">
  7.             <Path.Data>
  8.                 <PathGeometry>
  9.                     <PathGeometry.Figures>
  10.                         <PathFigureCollection>
  11.                             <PathFigure StartPoint="10,100">
  12.                                 <PathFigure.Segments>
  13.                                     <PathSegmentCollection>
  14.                                         <BezierSegment Point1="100,0" Point2="200,200" Point3="300,100" />
  15.                                         <BezierSegment Point1="100,0" Point2="200,200" Point3="300,100" />
  16.                                         <BezierSegment Point1="300,100" Point2="200,200" Point3="100,200" />
  17.                                         <BezierSegment Point1="50,50" Point2="100,100" Point3="150,150" />
  18.                                     </PathSegmentCollection>
  19.                                 </PathFigure.Segments>
  20.                             </PathFigure>
  21.                         </PathFigureCollection>
  22.                     </PathGeometry.Figures>
  23.                 </PathGeometry>
  24.             </Path.Data>
  25.         </Path>
  26.     </Grid>
  27. </Window>

WPF_12_Shapes_Fig11

 

Scarica la versione PDF dell'articolo. Scarica la versione Amazon Kindle dell'articolo.

Nessun commento: