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 : Aggregare stringhe con LINQ

Tip facile facile, ma a qualcuno potrebbe servire. Supponiamo di avere una lista di stringhe (magari come risultato di una query LINQ) e di voler ottenere una stringa con la concatenazione delle stesse: Dim list = CreateList() Dim concatStr = (From s In list _ Select s).Aggregate( Function (currentString, nextString) currentString + nextString) MessageBox.Show(concatStr) Il metodo CreateList non ci interessa, in questo momento, ma crea una lista di oggetti String. Protected Function CreateList() As IEnumerable( Of String ) Dim list As String () = {" stringa1 ", " stringa2 ", " stringa3 ", " stringa4 ", " stringa5 "} Return list.AsEnumerable() End Function Questo metodo potrebbe restituire una qualsiasi lista di oggetti di cui, nella select successiva recuperiamo solo stringhe. La stessa tecnica è utilizzabile per concatenare stringhe inserendovi un carattere separatore Dim list = CreateList() Dim

VB.NET: SplashScreen con effetto fade-in

In questo post vorrei proporvi un modo per realizzare una splash screen per le nostre applicazioni Windows Form che appare progressivamente con un effetto fade. Supponiamo di avere il nostro progetto VB.NET in una soluzione Visual Studio 2008 in cui abbiamo il sorgente della nostra applicazione Windows Form. Inseriamo una splash screen utilizzando il menù Progetto->Aggiungi Nuovo Elemento e selezionando il tipo di elemento “Schermata Iniziale” A questo punto Visual Studio creerà, automaticamente, la schermata iniziale che possiamo personalizzare graficamente come vogliamo. Per poter fare in modo che questa finestra appaia nel momento in cui avviamo l’applicazione, è necessario aprire le proprietà del progetto e impostare la maschera di avvio: In questo modo, all’avvio dell’applicazione, la schermata appare immediatamente e scompare un attimo prima della visualizzazione della finestra dell’applicazione. Possiamo far apparire la schermata iniziale con un ef

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: Public Sub New () If Not CreateWordApp() Then Throw New ApplicationException(" Assembly di interoperabilità con Office non trovato! ") End If End Sub Private _wordApp As Word.ApplicationClass Protected Function CreateWordApp() As Boolean Dim retval = True Try _wordApp = New Word.ApplicationClass() _wordApp.Visible = False Catch ex As System.Exception _wordApp = Nothing retval = False End Try Return retval End Function La conve