C11. La grafica
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 è instanaziabile. 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.
Tutto quello che si deve fare, in conclusione, è usufruire della proprietà Graphics esposta dal parametro "e" nell'evento Paint, comune a tutti i derivati di Control.
Ecco ora un elenco dei membri più importanti di Graphics:
Il tutto si divide in tre diversi sorgenti: una libreria di classi GraphItems, un controllo utente GraphArea e l'applicazione principale.
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:

Si aggiunga, quindi, un DrawArea con il nome di daGraph, una DataGridView di nome dgvValues e un pulsante di nome cmdDraw e testo "Disegna". Una volta posizionati i vari componenti, bisogna scrivere il codice:

Tutto quello che si deve fare, in conclusione, è usufruire della proprietà Graphics esposta dal parametro "e" nell'evento Paint, comune a tutti i derivati di Control.
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(
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)New Point(0, 0),New Point(50, 50), _New Size(200, 200)) - 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
- ScalteTransform(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)
Il tutto si divide in tre diversi sorgenti: una libreria di classi GraphItems, un controllo utente GraphArea 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 MustInheritClass GraphItem 'I colori possono essere sotto forma di Pen o Brush 'quindi metto due proprietà Private _ColorPenAs PenPrivate _ColorBrushAs BrushPublic Property ColorPen()As PenGet Return _ColorPenEnd Get Set (ByVal ValueAs Pen) _ColorPen = ValueEnd Set End Property Public Property ColorBrush()As BrushGet Return _ColorBrushEnd Get Set (ByVal ValueAs Brush) _ColorBrush = ValueEnd Set End Property 'Queste proprietà sono inutili: è possibile 'inizializzare un nuovo oggetto Pen o SolidBrush (derivato 'di Brush) con il costruttore che accetta un argomento di 'tipo Color. Tuttavia per ora lascio così Public Shared ReadOnly Property CommonPens(ByVal IndexAs Byte )As PenGet Select Case IndexCase 0Return Pens.RedCase 1Return Pens.OrangeCase 2Return Pens.YellowCase 3Return Pens.GreenCase 4Return Pens.LightBlueCase 5Return Pens.BlueCase 6Return Pens.VioletCase 7Return Pens.BlackCase Else Return Pens.WhiteEnd SelectEnd Get End Property Public Shared ReadOnly Property CommonBrushes(ByVal IndexAs Byte )As BrushGet Select Case IndexCase 0Return Brushes.RedCase 1Return Brushes.OrangeCase 2Return Brushes.YellowCase 3Return Brushes.GreenCase 4Return Brushes.LightBlueCase 5Return Brushes.BlueCase 6Return Brushes.VioletCase 7Return Brushes.BlackCase Else Return Brushes.WhiteEnd SelectEnd Get End Property 'Ogni elemento deve esporre la procedura Draw, 'per mezzo della quale il controllo GraphArea lo disegnerà 'sulla propria superfice. G sarà l'oggetto 'Graphics associato al controllo. Public MustOverride Sub Draw(ByVal GAs Graphics)End Class 'Un pezzo di torta XD Public Class PiePieceInherits GraphItem 'I parametri necessari a disegnarla sono: il centro 'della torta, il raggio, l'ampiezza (in gradi) e 'l'angolo iniziale Private _CenterAs PointPrivate _RadiusAs Int32Private _StartAngle, _EndAngleAs Single 'Centro Public Property Center()As PointGet Return _CenterEnd Get Set (ByVal ValueAs Point) _Center = ValueEnd Set End Property 'Raggio Public Property Radius()As Int32Get Return _RadiusEnd Get Set (ByVal ValueAs Int32) _Radius = ValueEnd Set End Property 'Angolo di partenza Public Property StartAngle()As Single Get Return _StartAngleEnd Get Set (ByVal ValueAs Single ) _StartAngle = ValueEnd Set End Property 'Ampiezza Public Property SweepAngle()As Single Get Return _EndAngleEnd Get Set (ByVal ValueAs Single ) _EndAngle = ValueEnd Set End Property Sub New (ByVal CenterAs Point,ByVal RadiusAs Int32, _ByVal StartAngleAs Single ,ByVal SweepAngleAs Single )Me .Center = CenterMe .Radius = RadiusMe .StartAngle = StartAngleMe .SweepAngle = SweepAngleEnd Sub Public Overrides Sub Draw(ByVal GAs Graphics) 'Calcola il quadrato in cui è inscritta la circonferenza 'della quale lo spicchio fa parte Dim UpperLeftAs New Point(Me .Center.X -Me .Radius, _Me .Center.Y -Me .Radius) 'Calcola la dimensione di tale quadrato Dim SizeAs New Size(Me .Radius * 2,Me .Radius * 2) 'Riempie il pezzo di torta con il colore G.FillPie(Me .ColorBrush,New Rectangle(UpperLeft, Size), _Me .StartAngle,Me .SweepAngle) 'Quindi disegna il contorno del pezzo in nero 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 ColorLabelInherits GraphItem 'I parametri necessari a disegnarla sono: il testo, 'le coordinate e il colore, che viene definito 'nella classe base Private _TextAs String Private _LocationAs Point 'Testo Public Property Text()As String Get Return _TextEnd Get Set (ByVal ValueAs String ) _Text = ValueEnd Set End Property 'Coordinate Public Property Location()As PointGet Return _LocationEnd Get Set (ByVal ValueAs Point) _Location = ValueEnd Set End Property Sub New (ByVal TextAs String )Me .Text = TextEnd Sub Public Overrides Sub Draw(ByVal GAs 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
GraphArea
Il controllo utente non è altro che una classe al cui interno coesistono controlli esistenti strettamente legati tra di loro da un unico
codice. Assomiglia un pò alla creazione di un form. Per aggiungere un nuovo controllo utente, cliccare col destro, nel solution explorer,
sul nome del progetto, quindi selezionare Add Item e poi User Control. Arrivati qui, basta impostare il colore di sfondo (BackColor) su
bianco. Il codice è molto semplice, poichè il controllo in questo caso ha la sola funzione di fungere da "tela" su cui
disegnare. Ecco il codice:
Public Class DrawArea 'Collezione degli elementi da disegnare Private _ItemsAs New List(Of GraphItem)Public ReadOnly Property Items()As List(Of GraphItem)Get Return _ItemsEnd Get End Property Private Sub DrawArea_Paint(ByVal senderAs Object , _ByVal eAs PaintEventArgs)Handles My Base.Paint 'Attiviamo l'anti-alias 'Così il disegno diventa più fluido e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAliasFor Each ItemAs GraphItemIn Me .Items Item.Draw(e.Graphics)Next End Sub End Class
L'applicazione principale
Prima di proseguire, bisogna far sì che DrawArea entri nella toolbox insieme agli altri controlli: cliccare su Build->Build [Nome progetto].
Ora DrawArea apparià nella toolbox ai primissimi posti, con l'icona di un ingranaggio viola:
Nuovo controllo DrawArea
Si aggiunga, quindi, un DrawArea con il nome di daGraph, una DataGridView di nome dgvValues e un pulsante di nome cmdDraw e testo "Disegna". Una volta posizionati i vari componenti, bisogna scrivere il codice:
Ed ecco un esempio di come si presenterà alla fine, tutta l'applicazione:Class Form1Private Sub cmdDraw_Click(ByVal senderAs Object ,ByVal eAs EventArgs) _Handles cmdDraw.ClickDim TotalAs Single daGraph.Items.Clear() 'Calcola il totale For Each RowAs DataGridViewRowIn dgvValues.Rows 'Controlla che il valore sia diverso da NULL If Row.CellsIs Nothing Then Continue For End If 'Quindi somma il valore della cella al totale Total += Row.Cells(0).ValueNext 'Costruisce gli spicchi 'Valore di una riga Dim ValueAs Single 'Variabile ausiliare del ciclo: tiene traccia dell'angolo a cui 'si è arrivati Dim PrevAngleAs Single = 0 'Anche questa, come sopra: tiene traccia a quale altezza si 'è arrivati con la legenda Dim YAs Int32 = 20 'Testo della riga Dim TextAs String 'Colore della riga Dim ColorAs Byte 'Una etichetta della legenda Dim LabAs ColorLabel 'Un pezzo della torta Dim PieceAs PiePieceFor Each RowAs DataGridViewRowIn dgvValues.Rows 'Controlla che i valori esistano e che la cella non 'sia l'ultima (che è sempre vuota) If Row.CellsIs Nothing OrElse _ Row.Index = dgvValues.RowCount - 1Then 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).ValueCase "Rosso" Color = 0Case "Arancio" Color = 1Case "Giallo" Color = 2Case "Verde" Color = 3Case "Azzurro" Color = 4Case "Indaco" Color = 5Case "Viola" Color = 6Case "Nero" Color = 7End Select 'Inizializza la nuova etichetta Lab =New ColorLabel(Text) Lab.ColorPen = GraphItem.CommonPens(Color) Lab.ColorBrush = GraphItem.CommonBrushes(Color) 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.ColorPen = GraphItem.CommonPens(Color) Piece.ColorBrush = GraphItem.CommonBrushes(Color) 'Tiene traccia dell'angolo PrevAngle += Value * 360 / Total 'Si sposta più in giù per la prossima etichetta Y += 20 'Aggiunge gli elementi daGraph.Items.Add(Lab) daGraph.Items.Add(Piece)Next 'E aggiorna il rendering daGraph.Refresh()End Sub End Class
Et voilà!
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



