Appunti di Programmazione

Creative Commons License

L'oggetto Graphics

La classe Graphics contiene i metodi per disegnare Testo, Immagini, Linee, Rettangoli e altre forme su un controllo; richiede l'utilizzo di una penna (Pen) per disegnare un contorno, o un pennello (Brush) per rappresentare una superficie piena.
Bisogna pensare all'oggetto Graphics come al foglio sul quale disegnare, scrivere, colorare e riprodurre immagini. Senza tale oggetto non è possibile esguire alcuna azione di tipo grafico, pertanto è fondamentale disporre di un'istanza di questa classe.

Ma come creare tale oggetto?

Tutti i controlli derivati dalla classe Control hanno a disposizione l'evento Paint, il quale viene generato/richiamato automaticamente ogni volta che si rende necessario aggiornare la visualizzazione del controllo (ridimensionamento della finestra, riduzione a icona, sovrapposizione di un'altra finestra...). Attraverso l'evento Paint si accede a PaintEventArgs che contiene le informazioni necessarie per il rendering di un controllo, ossia un riferimento ad un oggetto grafico e ad un oggetto rettangolo che rappresenta l'area in cui effettuare il disegno.
Dobbiamo ottenere l'oggetto Graphics di ciascuna chiamata al metodo poiché le proprietà del contesto Grafico che l'oggetto Graphics rappresenta posso variare.

Importante: non inserire comandi grafici all'interno dell'evento Load di un controllo, poiché l'evento Paint non è stato ancora richiamato e il disegno non viene generato.

1 - Metodo - OnPaint (NON richiede il rilascio delle risorse)

Uno dei sistemi più comuni per recuperare un oggetto Graphics in un controllo, è quello di sovrascrivere il metodo OnPaint il quale contiene un riferimento a PaintEventArgs da cui si ottiene il nostro oggetto Graphics. La chiamata ad OnPaint innesca l'evento Paint per il controllo, ridisegnando la sua superficie.
Per fare ciò è necessario creare una classe derivata all'interno della quale definire il nuovo metodo.

Per una Form:

Public Class Form1

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)

        Dim g As Graphics = e.Graphics
        g.DrawEllipse(Pens.IndianRed, 10, 10, 250, 130)
        MyBase.OnPaint(e)

    End Sub

End Class

Per un Button:

Creare una nuovo progetto e nominatelo "Overrides Pulsante". Aggiungete una nuova classe e assegnatele il nome "NewButton" e inserite il seguente codice:

Public Class NewButton

    Inherits Button
    'Inserite una vostra immagine completa del percorso.
    Dim miaImmagine As Bitmap = New Bitmap("D:\miaImmagine.jpg")

    Protected Overrides Sub OnPaint(ByVal pevent As System.Windows.Forms.PaintEventArgs)

        MyBase.OnPaint(pevent)
        pevent.Graphics.DrawImage(miaImmagine, 0, 0, Me.Size.Width, Me.Size.Height)

    End Sub

End Class

Adesso aprite il menu Genera(VB2005) o Compila(VB2008) e selezionate la prima voce: "Genera Overrides Pulsante"(VB2005) o "Compila Overrides Pulsante", in modo che la classe appena creata venga riconosciuta e inserito il nuovo controllo nella Casella degli Strumenti, così da poter essere usato.
Passate alla finestra progettazione della classe Form1. Dalla ToolBox selezionate il nuovo controllo NewButton e trascinatelo sulla Form impostando delle dimensioni sufficienti a visionarne il contenuto.
Verrà rappresentato un pulsante con un'immagine disegnata sopra che lo copre totalmente.

Talvolta anziché ridefinire il metodo OnPaint, viene creato un gestore di evento Paint personalizzato e usato per ridefinire il disegno senza passare da OnPaint:

Public Class Form1

    Private WithEvents NewPictureBox1 As New PictureBox

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(300, 200)

        With NewPictureBox1
            .Size = New Size(100, 80)
            .Location = New Point(100, 50)
            .BackColor = Color.Snow
        End With

        Me.Controls.Add(NewPictureBox1)

    End Sub

    Protected Sub myEventHandler_Paint(ByVal sender As Object, ByVal e As PaintEventArgs)

        e.Graphics.FillRectangle(Brushes.Blue, 10, 10, 56, 25)

    End Sub

    Private Sub NewPictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles NewPictureBox1.Paint

        myEventHandler_Paint(Me, e)

    End Sub

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles Me.Paint

        myEventHandler_Paint(Me, e)

    End Sub

End Class

Una volta creato l'evento Paint personalizzato, lo si può richiamare laddove ci è utile, magari nel corrispondente evento Paint di diversi controlli in modo da disegnare le stesse cose su più superfici.

2 - Metodo OnPaintBackground (NON richiede il rilascio delle risorse)

Questo metodo disegna lo sfondo, e quindi la forma delle finestra; risulta più veloce rispetto a OnPaint in quanto quest'ultimo si occupa di tutti i dettagli del disegno richiedendo un maggior tempo di rendering.
Come per il metodo precedente, anche in questo caso, per sovrascrivere il metodo per un controllo, bisogna creare una classe derivata all'interno della quale definire il nuovo metodo.

Per una Form:

Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)

    MyBase.OnPaintBackground(e)
    Dim g As Graphics = e.Graphics
    g.FillRectangle(Brushes.SlateBlue, 20, 30, 120, 56)

End Sub

Per una PictureBox:

Creare una nuovo progetto e nominatelo "Overrides CasellaImmagine".
Aggiungete una nuova classe e assegnatele il nome "NewPictureBox" e inserite il seguente codice:

Public Class NewPictureBox

    Inherits PictureBox

    Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)

        MyBase.OnPaintBackground(pevent)
        pevent.Graphics.FillPie(Brushes.DarkOrange, 34, 45, 100, 100, 0, 125)
        pevent.Graphics.FillPie(Brushes.DarkRed, 34, 45, 100, 100, 125, 17)
        pevent.Graphics.FillPie(Brushes.DarkSeaGreen, 34, 45, 100, 100, 142, 82)
        pevent.Graphics.FillPie(Brushes.DarkTurquoise, 34, 45, 100, 100, 224, 136)

    End Sub

End Class

Come avete fatto per OnPaint, adesso aprite il menu Genera o Compila e selezionate la prima voce: "Genera Overrides CasellaImmagine" o "Compila Overrides CasellaImmagine", in modo che la classe appena creata venga riconosciuta e inserito il nuovo controllo nella Casella degli Strumenti.
Passate alla finestra progettazione della classe Form1. Dalla ToolBox selezionate il nuovo controllo NewPictureBox, e trascinatelo sulla Form impostando delle dimensioni sufficienti a visionare il contenuto.
Vedrete disegnato un grafico a torta.

3 - Evento Paint (NON richiede il rilascio delle risorse)

Come già anticipato precedentemente, tutti gli oggetti derivati da Control, dispongono di questo metodo che si attiva ogni volta che il controllo deve essere ridisegnato, quindi tale evento risulta il luogo ideale dove inserire i comandi gafici.

Per un Button:

Public Class Form1

    Private WithEvents Button1 As New Button

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(200, 150)

        With Button1
            .Size = New Size(80, 30)
            .Location = New Point(56, 80)
            .Text = "Disegna"
        End With

        Me.Controls.Add(Button1)

    End Sub

    Private Sub Button1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles Button1.Paint

        Dim g As Graphics = e.Graphics
        g.FillRectangle(Brushes.LightSkyBlue, 10, 5, 30, 10)

    End Sub

End Class

I tre metodi sopra esposti non CREANO un oggetto Graphics, bensì ne ottengono un riferimento nell'ambito di PaintEventArgs (Dim g As Graphics = e.Graphics), e poiché è solo un riferimento che agisce all'interno della procedura creata, esso ha un ciclo di vita che inizia e finisce con la routine in questione, pertanto non è necessario rilasciare le risorse occupate da questo oggetto.

*****

Possono presentarsi situazioni in cui bisogna recuperare un oggetto Graphics al di fuori dell'ambito di Paint; ad esempio, se vogliamo disegnare sullo schermo un rettangolo di dimensioni definite nel punto in cui clicchiamo con il mouse, è necessario utilizzare l'evento MouseClick e creare in esso un'oggetto Graphics, sul quale poi disegnare la nostra figura.
I sistemi esposti di seguito possono tornare utili in questi casi.
Poiché l'oggetto Graphics viene CREATO, è obbligo rilasciare le risorse prima della chiusura della routine.

4 - Metodo CreateGraphics (È NECESSARIO rilasciare le risorse)

In questo esempio si crea un oggetto Graphics nell'evento MouseClick di una PictureBox, in modo che cliccando con il mouse all'interno di essa, venga disegnato un piccolo rettangolo blu.

Public Class Form1

    Private WithEvents PictureBox1 As New PictureBox

    Private Sub PictureBox1_MouseClick(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseClick

        Dim gp As Graphics = PictureBox1.CreateGraphics
        gp.FillRectangle(Brushes.Blue, e.X, e.Y, 23, 12)
        gp.Dispose()

    End Sub

    Public Sub New()

        InitializeComponent()

        With PictureBox1
            .Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or _
                               System.Windows.Forms.AnchorStyles.Bottom) _
                               Or System.Windows.Forms.AnchorStyles.Left) _
                               Or System.Windows.Forms.AnchorStyles.Right),  _
                               System.Windows.Forms.AnchorStyles)
            .Dock = DockStyle.Fill
            .BackColor = Color.Snow
        End With

        Me.Controls.Add(PictureBox1)

    End Sub

End Class

È da tenere presente che, nel caso in cui la finestra venga ridotta a icona e poi ridimensionata, oppure venga coperta da un'altra applicazione e poi ridiventa visibile, i rettangoli blu dell'esempio precedente NON saranno ri-disegnati e scompariranno dal controllo sul quale erano stati rappresentati.
Si può risolvere questo inconveniente, memorizzando la posizione delle figure in una collection e imporre la lettura di queste coordinate ogni volta che se ne presenta la necessità ridisegnando il tutto con il metodo Paint.

Public Class Form1

    Private punti As New List(Of Point)
    Private WithEvents PictureBox1 As New PictureBox

    Private Sub PictureBox1_MouseDown(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown

        punti.Add(New Point(e.X, e.Y))
        PictureBox1.Invalidate()

    End Sub

    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As  _
    System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint

        Dim gp As Graphics = e.Graphics

        For Each p As Point In punti

            gp.FillRectangle(Brushes.Blue, p.X, p.Y, 23, 12)

        Next

    End Sub

    Public Sub New()

        InitializeComponent()

        With PictureBox1
            .Anchor = CType((((System.Windows.Forms.AnchorStyles.Top _
                               Or System.Windows.Forms.AnchorStyles.Bottom) _
                               Or System.Windows.Forms.AnchorStyles.Left) _
                               Or System.Windows.Forms.AnchorStyles.Right),  _
                               System.Windows.Forms.AnchorStyles)
            .Dock = DockStyle.Fill
            .BackColor = Color.Snow
        End With

        Me.Controls.Add(PictureBox1)

    End Sub

End Class

5 - Metodo FromImage (È NECESSARIO rilasciare le risorse)

La classe Image fornisce le funzionalità necessarie all'uso delle sue classi derivate Bitmap e Metafile.

La classe Bitmap consente di trattare con i seguenti tipi di files: BMP,GIF,JPEG,PNG,TIFF,EXIF

La classe Metafile consente di gestire i seguenti formati: WMF,EMF,EMF+

In GDI+ è possibile visualizzare tutti e tre i formati di Metafiles, ma non è consentito salvare il tipo WMF.
Con il metodo FromImage si può creare un oggetto Graphics dall'oggetto Image specificato.
Per usare questo metodo si procede in questo modo:

Public Class Form1

    Private WithEvents PictureBox1 As New PictureBox

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(500, 400)

        With PictureBox1
            .Size = New Size(450, 300)
            .Location = New Point(21, 5)
            .BackColor = Color.White
            .BorderStyle = BorderStyle.FixedSingle
        End With

        Me.Controls.Add(PictureBox1)

    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim bm As Bitmap = New Bitmap("D:\miaImmagine.jpg")
        Dim gr As Graphics = Graphics.FromImage(bm)
        gr.DrawRectangle(Pens.Gold, 10, 10, 80, 80)
        PictureBox1.Image = bm
        gr.Dispose()

    End Sub

End Class

Una cosa importante da sapere è che NON conviene aprire un'immagine e poi elaborarla, in quanto andremmo incontro a conflitti nella gestione del file in questione. Tanto per fare un esempio se apriamo una FOTO, la modifichiamo e poi tentiamo di salvarne le modifiche, si verifica un errore poiché la nostra immagine risulta già in uso e il sistema non ci consente di effettuare altre operazioni tranne quella in corso.
Si può risolvere questo inconveniente creando due Bitmap, la prima conterrà il file che vogliamo elaborare e la seconda sarà una copia della prima. A questo punto rilasciamo le risorse della prima Bitmap e lavoriamo sulla copia. Quando dobbiamo eseguire un salvataggio delle modifiche lo facciamo nel file originale (il primo), che essendo stato rilasciato risulta libero e non vincolato da qualche applicazione.
Vediamo come:

Public Class Form1

    Dim originale As Bitmap
    Dim copia As Bitmap
    Dim gr As Graphics

    Private WithEvents OpenFileDialog1 As New OpenFileDialog
    Private WithEvents SaveFileDialog1 As New SaveFileDialog
    Private WithEvents PictureBox1 As New PictureBox
    Private WithEvents ButtonApri As New Button
    Private WithEvents ButtonSalva As New Button

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(500, 400)

        With OpenFileDialog1

            .Filter = "(*.ipg)|*.jpg"
            .FilterIndex = 1
            .Title = "Carica Immagine"

        End With

        With SaveFileDialog1

            .Filter = "(*.ipg)|*.jpg"
            .FilterIndex = 1
            .Title = "Carica Immagine"

        End With

        With ButtonApri
            .Size = New Size(80, 30)
            .Location = New Point(139, 313)
            .Text = "Apri"
        End With

        With ButtonSalva
            .Size = New Size(80, 30)
            .Location = New Point(274, 313)
            .Text = "Salva"
        End With

        With PictureBox1
            .Size = New Size(450, 300)
            .Location = New Point(21, 5)
            .BackColor = Color.White
            .BorderStyle = BorderStyle.FixedSingle
        End With

        Me.Controls.Add(ButtonApri)
        Me.Controls.Add(ButtonSalva)
        Me.Controls.Add(PictureBox1)

    End Sub

    Private Sub ButtonApri_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles ButtonApri.Click

        If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then

            originale = New Bitmap(OpenFileDialog1.FileName)
            copia = New Bitmap(originale)
            originale.Dispose()
            gr = Graphics.FromImage(copia)
            gr.FillEllipse(Brushes.Blue, 50, 50, 200, 89)
            PictureBox1.Image = copia
            gr.Dispose()

        End If

    End Sub

    Private Sub ButtonSalva_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles ButtonSalva.Click

        If SaveFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then

            copia.Save(SaveFileDialog1.FileName)

        End If

    End Sub

End Class

6 - Metodo FromHwnd (È NECESSARIO rilasciare le risorse)

Consente di creare un oggetto Graphics dall'Handle di una finestra.
Ogni controllo ha un handle da esporre che può essere usato per creare l'oggetto Graphics.
Il codice che segue permette di disegnare una linea su una PictureBox usandone l'handle, quando viene premuto un pulsante.

Public Class Form1

    Dim g As Graphics
    Private WithEvents PictureBox1 As New PictureBox
    Private WithEvents Button1 As New Button

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(500, 400)

        With Button1
            .Size = New Size(80, 30)
            .Location = New Point(206, 313)
            .Text = "Disegna"
        End With

        With PictureBox1
            .Size = New Size(450, 300)
            .Location = New Point(21, 5)
            .BackColor = Color.White
            .BorderStyle = BorderStyle.FixedSingle
        End With

        Me.Controls.Add(Button1)
        Me.Controls.Add(PictureBox1)

    End Sub

    Private Sub Button1_Click1(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

        g = Graphics.FromHwnd(PictureBox1.Handle)
        g.DrawLine(Pens.Black, 10, 10, 156, 236)
        g.Dispose()

    End Sub

End Class

7 - Metodo FromHdc (È NECESSARIO rilasciare le risorse)

Questo metodo permette di creare un oggetto Graphics da un handle di un device context.
Detto in modo più semplice, il metodo Graphics.FromHdc consente di ottenere un oggetto Graphics da "una qualunque periferica su cui è possibile disegnare" (=Device Context): una stampante, una picturebox, lo schermo, una bitmap...
Per fare ciò è necessario disporre di un'istanza della struttura IntPtr che può rappresentare indifferentemente un puntatore o un handle, in questo caso specifico, ovviamente, viene utilizzato come Handle.
Attraverso il metodo Graphics.GetHdc() otteniamo l'handle per il device context che è associato all'oggetto Graphics. Quindi si crea un'istanza dell'oggetto Graphics ed il gioco è fatto.
È necessario rilasciare le risorse occupate dall'handle. Per fare questo si deve usare il metodo specifico Graphics.ReleaseHdc (NomeHandle)

GetHdc() e ReleaseHdc() devono essere eseguite in coppia.

Public Class Form1

    Private WithEvents PictureBox1 As New PictureBox
    Private WithEvents Button1 As New Button

    Public Sub New()

        InitializeComponent()

        Me.Size = New Size(300, 200)

        With Button1
            .Size = New Size(80, 30)
            .Location = New Point(106, 120)
            .Text = "Disegna"
        End With

        With PictureBox1
            .Size = New Size(200, 100)
            .Location = New Point(80, 5)
            .BackColor = Color.White
            .BorderStyle = BorderStyle.FixedSingle
        End With

        Me.Controls.Add(Button1)
        Me.Controls.Add(PictureBox1)

    End Sub

    Public Sub FromHdcHdc(ByVal e As PaintEventArgs)

        'Ottiene un handle ad un device context.
        Dim myHdc As IntPtr = e.Graphics.GetHdc()

        'Crea un oggetto Graphics usando l'handle al device context.
        Dim newGraphics As Graphics = Graphics.FromHdc(myHdc)

        'Disegna un rettangolo sulla periferica.
        newGraphics.DrawRectangle(New Pen(Color.Red, 3), 10, 15, 50, 30)

        'Rilascia le risorse occupate dall'handle.
        e.Graphics.ReleaseHdc(myHdc)

        'Usa il metodo Dispose per rilasciare le risorse dell'oggetto Graphics.
        newGraphics.Dispose()

    End Sub

    'Disegna sulla Form.
    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles Me.Paint

        FromHdcHdc(e)

    End Sub

    'Disegna su una PictureBox.
    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles PictureBox1.Paint

        FromHdcHdc(e)

    End Sub

    'Disegna su un Button.
    Private Sub Button1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
    Handles Button1.Paint

        FromHdcHdc(e)

    End Sub

End Class