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