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…

VB.NET for Dummies: Gli Eventi

Vorrei iniziare con questo post una serie dedicata ad aspetti di VB.NET di base che possono essere utile a coloro che si avvicinano al mondo .NET e che, in genere, non vengono trattati a livello base.La serie di post non ha la pretesa di essere assolutamente esaustivi sugli argomenti che tratteròIn questo primo post parleremo degli eventi.Cosa sono e a cosa servonoUn evento è la “notifica” dell’accadimento di qualcosa.Quando, ad esempio, premiamo un bottone della finestra di un’applicazione, dietro le quinte, il bottone stesso “notifica” al mondo circostante che qualcuno, in quell’istante, lo ha premuto.Sta, poi, al mondo circostante preoccuparsi di “intercettare” l’evento stesso per gestirlo (tramite un gestore di evento).Attenzione a non confondere la pressione del tasto con la “notifica” della pressione del tasto: l’evento è la “notifica” dell’accadimento, non l’accadimento stesso.Ma a cosa serve tutto ciò?Utilizzare gli eventi è un modo per disaccoppiare due o più attori del nostr…

Creare uno shortcut con VB.NET

Prendendo spunto da un post comparso sul forum MSDN vorrei proporvi un tip su come creare uno shortcut utilizzando VB.NET.Per poter creare uno shortcut possiamo procedere in due modi: o ci studiamo la struttura del file .lnk e scriviamo una classe che è in grado di ricreare tale struttura oppure utilizziamo Windows Scripting Host.La prima soluzione è percorribile ma laboriosa perchè la struttura di un file lnk non è banale. Chi fosse interessato a vedere come è composto, internamente, un file lnk può scaricare la seguente reference guide (link).Io vorrei proporvi la seconda strada e realizzerò una classe che incapsula l’utilizzo di Windows Scripting Host.L’object model di Windows Scripting Host è contenuto nella dll IWshRuntimeLibrary che può essere referenziata, nel nostro progetto, utilizzando il tab COM della finestra di aggiunta delle reference:Tra gli oggetti che troviamo all’interno della libreria utilizzeremo la classe WshShell e la classe WshShortcut.La prima delle due rappres…