Passa ai contenuti principali

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.

Commenti

Post popolari in questo blog

VB.NET: Convertire un file DOC in RTF e PDF con office interop

In questo post vorrei proporvi del codice per poter convertire un file .doc in un file .rtf oppure .pdf utilizzando le API di interoperabilità di Office.Creeremo una classe, DocConverter, che esporrà le due funzionalità sopra citate.Cominciamo con il prevedere un attributo privato della classe che rappresenterà l’applicazione Word che utilizzeremo per la conversione. Creeremo l’istanza dell’attributo privato all’interno del costruttore della classe:PublicSubNew()
IfNot CreateWordApp() Then
ThrowNew ApplicationException("Assembly di interoperabilità con Office non trovato!")
EndIf
EndSub
Private _wordApp As Word.ApplicationClass
ProtectedFunction CreateWordApp() AsBoolean
Dim retval = True
Try
_wordApp = New Word.ApplicationClass()
_wordApp.Visible = False
Catch ex As System.Exception
_wordApp = Nothing
retval = False
EndTry
Return retval
EndFunction

La conversione del file doc sarà effettuata aprendo il file stesso ed eseguendo un’operazione di SaveAs:

Pr…

Cambiare la lingua di Visual Studio 2008

Oggi ho avuto qualche problema installando Windows Mobile 6 Professional SDK Refresh e Windows Mobile 6 Standard SDK Refresh.Scaricati i file di installazione e installati, ho provato a creare un progetto di tipo Windows Mobile 6.0 e mi sono beccato questo errore:Dopo qualche smanettamento abbiamo scoperto (e ringrazio il mitico Matteo per l’aiuto) che il mio Visual Studio 2008, pur essendo in Inglese (prova ne era il fatto che gli hotfix e la SP installata erano nella lingua di Albione) aveva come lingua impostata quella del sistema operativo (italiano).Ovviamente, non avrebbe mai potuto trovare la cartella 1040 (italiano) visto che l’installazione dell’SDK aveva supposto che la lingua del Visual Studio fosse Inglese (1033).La soluzione del problema è duplice:1) Duplicate la cartella 1033 presente nel percorso evidenziato dall’errore e la rinominate 10402) cambiate la lingua di Visual Studio.Per questa ultima eventualità basta andare nel menù Strumenti/Opzioni:e cambiare il linguaggi…

Tascam DR-05 registratore digitale per tutti

Diverso tempo fa ho acquistato il registratore digitale Tascam DR-05 e, ora, dopo diversi mesi di utilizzo, posso dire la mia a proposito.

Si tratta di un ottimo registratore digitale con microfono stereo che permette di ottenere registrazioni di ottima qualitaà anche in ambienti non perfetti dal punto di vista acustico.

Interessante la possibilità di utilizzare un cavalletto di piccole dimensioni come HAMA Mini treppiede o Mini Cavalletto Universale per tenere il microfono sollevato dal tavolo in fase di registrazione grazie allàattacco universale per macchina fotografica che il microfono ha nella parte inferiore.

Da segnalare anche il menu’ ben fatto e la possibilita’ di utilizzare una scheda SD esterna per memorizzare i file audio. Anche a livello di consumo batterie non e’ niente male. Consiglio l’uso delle alcaline non ricaricabili.

Il mio utilizzo e’ stato prettamente di registrazione di podcast vocali (no musica) ma le recensioni confermano che se la cava egregiamente con la mu…