Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - La classe Graphics

Guida al Visual Basic .NET

Capitolo 96° - La classe Graphics

<< Precedente Prossimo >>

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:

PieChart.jpg

<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...