|
La grafica è una delle parti meno usate, o meno comprese, del Framework .NET. Essenzialmente serve a disegnare tutto quello che
il supporto .NET di per sé non è progettato per fare. Ad esempio, si possono creare grafici, modificare immagini e riprodurre
effetti particolari. Tutta l'infrastruttura di controllo della grafica si basa su una classe portante, chiamata Graphics, che non possiede
alcun costruttore: per questo motivo non è istanaziabile. Dopo aver chiarito un concetto del genere, dovrebbe sorgere spontaneamente
il dubbio su come si possa fare, allora, per usarla, dato che non espone metodi statici e che non può essere inizializzata. La risposta
è semplice: ogni controllo possiede un proprio oggetto Graphics associato, per mezzo del quale viene disegnato sullo schermo e grazie
a cui il programmatore interviene nella sua visualizzazione. Questo fa pensare che in realtà il costruttore esista, ma sia specificato
come Private (o al massimo Friend) e perciò accessibile solo all'interno degli oscuri meccanismi .NET, i quali si occupano di fornirne uno a ogni controllo
durante la costruzione dell'interfaccia. Bisogna comunque ricordare che ci sono metodi statici factory per la crezione di Graphics a partire
da altre immagini o da altre finestre, ma nessuna fornisce un nuovo oggetto vuoto. N.B.: Diversamente dall'approccio adottato nelle versioni precedenti della guida, non useremo l'evento Paint per disegnare su un controllo. Tale
evento viene generato ogniqualvolta un controllo deve essere ridisegnato sullo schermo e perciò, se ne facessimo uso, faremmo
eseguire lo stesso codice più volte senza bisogno. Piuttosto, useremo un'alternativa più elengate e decisamente più
performante. Creeremo una nuova immagine vuota, associandovi un oggetto Graphics, e disegneremo su questa immagine, che verrà
poi depositata su un controllo (o sul suo sfondo). È possibile eseguire questa operazione fino a un centinaio di volte al secondo
senza che l'utente si accorga di quei fastidiosi sfarfalii che si avvertono quando si inserisce il codice di disegno nell'evento Paint.
Ecco ora un elenco dei membri più importanti di Graphics:
- Clear(C) : cancella tutto il contenuto di Graphics, nei suoi margini, e lo rimpie con un colore uniforme definito da C
- CompositingMode : determina il modo in cui due o più immagini sovrapposte vengano disegnate. È determinato da un enumeratore
che assume solamente due valori: SourceCopy (la parte più recente rimpiazza quella esistente, "sovrascrivendo" i propri colori sui
vecchi) e SourceOver (le due parti vengono sovrapposte in modo da formare una sfumatura, in cui entra prepotentemente in gioco il fattore Alpha,
ossia la trasparenza, che determina quale dei due colori prevalga sull'altro e come debbano essere miscelati)
- CompositingQuality : determina la qualità dell'operazione di composizione sopra illustrata. I valori dell'enumeratore sono pochi,
e permettono di scegliere se ottenere una maggior qualità o una maggior velocità oppure se considerare il fattore di correzione
gamma
- CopyFromScreen(P1, P2, Size) : questa procedura è davvero molto utile. Permette di catturare una parte dello schermo e riprodurla
sul supporto dell'oggetto Graphics (che, in definitiva, è la superificie del controllo a cui esso appartiene). Accetta tre argomenti:
il primo, P1 As Point, determina il margine superiore sinistro della regione dello schermo da cui prelevare l'immagine; il secondo, P2 As Point,
determina il margine superiore sinistro della regione di Graphics su cui copiare l'immagine; l'ultimo è di tipo Size e specifica
larghezza e altezza della regione da prelevare. Ad esempio, si supponga che questo codice sia eseguito nell'evento Paint del form:
e.Graphics.CopyFromScreen(New Point(0, 0), New Point(50, 50), _
New Size(200, 200))
Ebbene, il quadrato di lato 200 pixel che inizia nel punto (0,0) dello schermo (ossia in alto a sinistra), verrà copiato nella superficie
del form a partire dal punto (50,50)
- DpiX, DpiY : restituiscono rispettivamente la risoluzione su X e su Y, in punti per pixel
- Draw... : tutte le procedure che iniziano per "Draw" permettono di disegnare l'elemento corrispondente sul supporto di Graphics. A
seconda dell'entità geometrica, cambiano i parametri, che sono sempre visibili grazie al fumetto che li suggerisce
- Fill... : tutte le procedura che iniziano per "Fill" disegnano una figura e la riempiono con lo stesso colore
- FromHdc(Ptr) : inizializza e restituisce un oggetto Graphics partendo da un'immagine: di tale immagine si dispone solo di un puntatore
intero (System.IntPtr). Può essere utilizzata in rari casi, ad esempio nel Marshalling di oggetti
- FromHwnd(Ptr) : inizializza e restituisce un oggetto Graphics partendo da una finestra: di tale finestra si dispone solo dell'handle
- FromImage(Img) : inizializza e restituisce un oggetto Graphics partendo da un'immagine Img As Image
- RenderingOrigin : specifica quale sia l'origine del rendering, ossia il punto considerato (0,0)
- ResetTransorm : annulla ogni trasformazione
- RotateTransform(A) : effettua una rotazione di A gradi su tutti gli elementi di Graphics
- ScaleTransform(sX, sY) : effettua una trasformazione delle dimensioni, mltiplicandole per sX su X e per sY su Y
- SmoothingMode : determina quale modalità usare per smussare linee e percorsi curvi. Per un buon risultato si può usare
l'anti-alias, che riduce di molto le sgranature evidenti prodotte dai pixel
- Transformation : restituisce o imposta una matrice che rappresenta tutti i cambiamenti dello spazio 2D
- TranslateTransform(dX, dY) : trasla tutto il contenuto di Graphics di un vettore (dX, dY)
Nell'esempio che segue, scriverò un programma per disegnare grafici a torta bidimensionali.
Il tutto si divide in due diversi sorgenti: una libreria di classi GraphItems e l'applicazione principale.
GraphItems
La libreria espone tre classi. La prima è GraphItem, una classe astratta che rappresenta la base per gli altri elementi. Si usa questo
tipo di tecnica poichè servirà immagazzinare diversi tipi di elementi in una sola lista: per evitare liste a tipizzazione
debole come ArrayList, si usa una lista a tipizzazione forte in cui il tipo generics collegato è costituito da una classe base
comune a tutte. Accade molto spesso di usare questa tecnica, perciò fate attenzione.
La seconda classe esposta rappresenta uno spicchio del grafico a torta e contiene le informazioni e la procedura per poterlo disegnare. La
terza, invece, rappresenta l'etichetta corrispondente al colore nella legenda: il risultato che visualizzerà sullo schermo è
un quadratino colorato al cui fianco presenzia la didascalia associata al colore e il suo valore. Ecco il codice:
'Questa classe astratta costituisce la base per ogni
'elemento che andrà ad essere disegnato sul controllo
Public MustInherit Class GraphItem
'Per disegnare delle forme geometriche con i metodi Draw
'si usano oggetti di tipo Pen (penna): una penna definisce
'il colore usato per tracciare le linee e il loro
'spessore. Sono presenti delle penne predefinite nella
'classe statica Pens: una per ogni colore (per tutte,
'l'ampiezza del tratto è costante e pari a 1).
'Noi useremo sempre delle penne nere per il contorno
'dei prossimi oggetti, ma ho voluto aggiungere
'questo membro per completezza.
Private _ColorPen As Pen
'Allo stesso modo, per riempire delle forme deometriche
'coi metodi Fill, si usano i pennelli (Brush). Brush è
'una classe astratta che costituisce la base di tutti
'i pennelli derivati. Noi useremo dei SolidBrush, oggetti
'che colorano un'area con colore uniforme. Tuttavia, come
'spiegherò in seguito, esistono molti altri tipi
'di pennelli, ad esempio per eseguire sfumature o per
'riempire un'area con delle immagini
Private _ColorBrush As Brush
Public Property ColorPen() As Pen
Get
Return _ColorPen
End Get
Set(ByVal Value As Pen)
_ColorPen = Value
End Set
End Property
Public Property ColorBrush() As Brush
Get
Return _ColorBrush
End Get
Set(ByVal Value As Brush)
_ColorBrush = Value
End Set
End Property
'Ogni elemento deve esporre la procedura Draw,
'per mezzo della quale esso disegnerà la propria
'rappresentazione sul supporto grafico specificato
'da G. Come già accennato, disegneremo tutto
'su un'immagine vuota creata da noi
Public MustOverride Sub Draw(ByVal G As Graphics)
End Class
'Un pezzo di torta XD
Public Class PiePiece
Inherits GraphItem
'I parametri necessari a disegnarla sono: il centro
'della torta, il raggio, l'ampiezza (in gradi) e
'l'angolo iniziale
Private _Center As Point
Private _Radius As Int32
Private _StartAngle, _EndAngle As Single
'Centro
Public Property Center() As Point
Get
Return _Center
End Get
Set(ByVal Value As Point)
_Center = Value
End Set
End Property
'Raggio
Public Property Radius() As Int32
Get
Return _Radius
End Get
Set(ByVal Value As Int32)
_Radius = Value
End Set
End Property
'Angolo di partenza
Public Property StartAngle() As Single
Get
Return _StartAngle
End Get
Set(ByVal Value As Single)
_StartAngle = Value
End Set
End Property
'Ampiezza
Public Property SweepAngle() As Single
Get
Return _EndAngle
End Get
Set(ByVal Value As Single)
_EndAngle = Value
End Set
End Property
Sub New(ByVal Center As Point, ByVal Radius As Int32, _
ByVal StartAngle As Single, ByVal SweepAngle As Single)
Me.Center = Center
Me.Radius = Radius
Me.StartAngle = StartAngle
Me.SweepAngle = SweepAngle
End Sub
Public Overrides Sub Draw(ByVal G As Graphics)
'Calcola il quadrato in cui è inscritta la circonferenza
'della quale lo spicchio fa parte
Dim UpperLeft As New Point(Me.Center.X - Me.Radius, _
Me.Center.Y - Me.Radius)
'Calcola la dimensione di tale quadrato
Dim Size As New Size(Me.Radius * 2, Me.Radius * 2)
'Riempie il pezzo di torta con il colore. FillPie
'riempie col pennello specificato un settore circolare
'dell'ellisse inscritto nel rettangolo passato come
'parametro, a partire dall'angolo StartAngle,
'spazzando un angolo SweepAngle
G.FillPie(Me.ColorBrush, New Rectangle(UpperLeft, Size), _
Me.StartAngle, Me.SweepAngle)
'Quindi disegna il contorno del pezzo in nero. Gli
'argomenti sono gli stessi, ad eccezione della penna
'al posto del pennello. Pens.Black è una
'penna nera di tratto 1
G.DrawPie(Pens.Black, New Rectangle(UpperLeft, Size), _
Me.StartAngle, Me.SweepAngle)
End Sub
End Class
'Un'etichetta che visualizza il colore e il testo
'corrispondente
Public Class ColorLabel
Inherits GraphItem
'I parametri necessari a disegnarla sono: il testo,
'le coordinate e il colore, che viene definito
'nella classe base
Private _Text As String
Private _Location As Point
'Testo
Public Property Text() As String
Get
Return _Text
End Get
Set(ByVal Value As String)
_Text = Value
End Set
End Property
'Coordinate
Public Property Location() As Point
Get
Return _Location
End Get
Set(ByVal Value As Point)
_Location = Value
End Set
End Property
Sub New(ByVal Text As String)
Me.Text = Text
End Sub
Public Overrides Sub Draw(ByVal G As System.Drawing.Graphics)
'Disegna un quadratino colorato
G.FillRectangle(Me.ColorBrush, New Rectangle(Me.Location, _
New Size(20, 10)))
'Disegna il contorno nero al quadratino
G.DrawRectangle(Pens.Black, New Rectangle(Me.Location, _
New Size(20, 10)))
'Disegna il testo
'New Font... inizializza un nuovo font, ossia Microsoft
'Sans Serif di dimensione 12, senza stili aggiuntivi
G.DrawString(Me.Text, New Font("Microsoft Sans Serif", 12, FontStyle.Regular), Brushes.Black, Me.Location.X + 30, Me.Location.Y - 5)
End Sub
End Class
L'applicazione principale
L'applicazione principale contiene due componenti: un DataGridView e una PictureBox. Per vedere come li ho impostati, guardate lo screenshot
in fondo alla pagina.
Class Form1
Private Items As New List(Of GraphItem)
Private Sub cmdDraw_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles cmdDraw.Click
Dim Total As Single
Items.Clear()
'Calcola il totale
For Each Row As DataGridViewRow In dgvValues.Rows
'Controlla che il valore sia diverso da NULL
If Row.Cells Is Nothing Then
Continue For
End If
'Quindi somma il valore della cella al totale
Total += Row.Cells(0).Value
Next
'Costruisce gli spicchi
'Valore di una riga
Dim Value As Single
'Variabile ausiliare del ciclo: tiene traccia dell'angolo a cui
'si è arrivati
Dim PrevAngle As Single = 0
'Anche questa, come sopra: tiene traccia a quale altezza si
'è arrivati con la legenda
Dim Y As Int32 = 20
'Testo della riga
Dim Text As String
'Pennello > colore
Dim Br As Brush
'Una etichetta della legenda
Dim Lab As ColorLabel
'Un pezzo della torta
Dim Piece As PiePiece
For Each Row As DataGridViewRow In dgvValues.Rows
'Controlla che i valori esistano e che la cella non
'sia l'ultima (che è sempre vuota)
If Row.Cells Is Nothing OrElse _
Row.Index = dgvValues.RowCount - 1 Then
Continue For
End If
Value = Row.Cells(0).Value
'Costruisce il testo della legenda, formato da quello della
'riga, con la specificazione, tra parentesi, del valore
'corrispondente e della percentuale
Text = String.Format("{0} ({1:N2} - {2:N2}%)", _
Row.Cells(1).Value, Value, Value * 100 / Total)
'Questo sempre per l'intelligenza di DataGridView,
Select Case Row.Cells(2).Value
Case "Rosso"
Br = Brushes.Red
Case "Arancio"
Br = Brushes.Orange
Case "Giallo"
Br = Brushes.Yellow
Case "Verde"
Br = Brushes.Green
Case "Azzurro"
Br = Brushes.LightBlue
Case "Indaco"
Br = Brushes.Blue
Case "Viola"
Br = Brushes.Violet
Case "Nero"
Br = Brushes.Black
End Select
'Inizializza la nuova etichetta
Lab = New ColorLabel(Text)
Lab.ColorBrush = Br
Lab.Location = New Point(280, Y)
'E il nuovo pezzo di torta. Value * 360 / Totale è
'l'ampiezza dell'angolo, ottenuta con la proporzione:
'Value : Total = x : 360
Piece = New PiePiece(New Point(150, 125), 100, _
PrevAngle, Value * 360 / Total)
Piece.ColorBrush = Br
'Tiene traccia dell'angolo
PrevAngle += Value * 360 / Total
'Si sposta più in giù per la prossima etichetta
Y += 20
'Aggiunge gli elementi
Items.Add(Lab)
Items.Add(Piece)
Next
'Crea una nuova immagine vuota
Dim Img As New Bitmap(imgPreview.Width, imgPreview.Height)
'Prende l'oggetto Graphics associato a quell'immagine
Dim G As Graphics = Graphics.FromImage(Img)
'Attiva l'anti-alias
G.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'E disegna ogni elemento
For Each Item As GraphItem In Me.Items
Item.Draw(G)
Next
'Ogni cosa disegnata mediante G verrà trasferita
'sull'immagine Img associata
G.Flush()
imgPreview.Image = Img
End Sub
End Class
Ed ecco un esempio di come si presenterà alla fine, tutta l'applicazione:
 |
|