Creación de una clase para dibujar en Windows Forms usando doble buffer

Fecha: 05/Oct/2004 (4 de Octubre de 2004)
Autor: Braulio Núñez Lanza

 


DESARROLLANDO LA CLASE:  

 

Este artículo describe cómo crear una clase que nos permita dibujar en un formulario o en un control mediante GDI+, utilizando un doble buffer para evitar el parpadeo de las imágenes. Esta clase nos permitirá abstraernos de todos los detalles que conlleva el dibujo mediante GDI+, facilitando la realización de gráficos. Además se desarrolla una pequeña clase que nos servirá para calcular el número de frames por segundo dibujados.

            Todo el código estará escrito en Visual Basic.NET, sin embargo, es perfectamente traducible a C#.

 

El primer paso será crear un nuevo proyecto del tipo “aplicación para windows”. A dicho proyecto le agregaremos una clase a la que llamaremos Grafico.

 

Normalmente, para dibujar en un formulario o en otro control, lo que hacemos es dibujar directamente sobre su superficie a través del objeto graphics obtenido a partir del formulario o control, llamando a su método createGraphics.

El fundamento de la técnica del doble buffer consiste en no dibujar directamente sobre el formulario, sino en hacerlo sobre un objeto Bitmap mediante un segundo objeto graphics creado a partir de dicha imagen (usando el método shared fromImage de la clase graphics). Una vez terminado de dibujar sobre el buffer, lo que haremos será dibujar sobre el objeto graphics (creado a partir del formulario) la imagen que hemos utilizado de buffer.

 

Para realizar todo esto incluiremos en nuestra clase Grafico las siguientes variables privadas:

 

    Dim Grafico As Graphics

    Dim ImgBuffer As Bitmap

    Dim GraficoBuffer As Graphics

    Dim ColorFo ndo As Color

 

La variable ColorFondo será utilizada para guardar el color del formulario o control en el cual queremos dibujar, para que cada vez que queramos dibujar en él podamos borrar la imagen poniéndola completamente del color que tenia inicialmente.

 

A continuación procederemos a crear el constructor:

 

    Public Sub New(ByVal controlADibujar As Control)

        Grafico = controlADibujar.CreateGraphics

        ImgBuffer = New Bitmap(controlADibujar.Width, controlADibujar.Height)

        GraficoBuffer = Graphics.FromImage(ImgBuffer)

        ColorFondo = controlADibujar.BackColor

    End Sub

 

El constructor recibe el control sobre el que se va a dibujar. A partir de este control creamos un objeto graphics, mediante el cual se dibujará la imagen en el control. Creamos también un bitmap del tamaño del control y un grafico a partir de este bitmap.

 

            El siguiente método de nuestra clase se encargará de borrar el dibujo rellenando todo el control de su color de fondo:

    Public Sub Borrar()

        GraficoBuffer.Clear(ColorFondo)

    End Sub

 

 

Sólo he incluido algunos métodos a nuestra clase, sin embargo se pueden incluir muchos más para agregar nueva funcionalidad a la clase, como por ejemplo métodos para dibujar rectángulos, diferentes tipos de degradados, regiones, distintas formas como flechas, arcos, etc.

 

    'Dibuja la imagen con su tamaño original

    Public Overloads Sub DibujarImagen(ByVal x As Integer, ByVal y As Integer, ByVal fichImagen As String)

        Dim imagen As Bitmap = New Bitmap(fichImagen)

 

        GraficoBuffer.DrawImage(imagen, x, y)

    End Sub

 

    'Dibuja la imagen con el tamaño que se le especifique

    Public Overloads Sub DibujarImagen(ByVal x As Integer, ByVal y As Integer, ByVal ancho As Integer, ByVal alto As Integer, ByVal fichImagen As String)

        Dim imagen As Bitmap = New Bitmap(fichImagen)

 

        GraficoBuffer.DrawImage(imagen, x, y, ancho, alto)

    End Sub

 

    Public Sub DibujarCirculo(ByVal x As Integer, ByVal y As Integer, ByVal radio As Integer, ByVal color As Color)

        Dim brocha As New SolidBrush(color)

        Dim lapiz As New Pen(brocha)

 

        GraficoBuffer.DrawEllipse(lapiz, x, y, radio, radio)

        GraficoBuffer.FillEllipse(brocha, x, y, radio, radio)

    End Sub

 

    Public Overloads Sub DibujarRectangulo(ByVal x As Integer, ByVal y As Integer, ByVal ancho As Integer, ByVal alto As Integer, ByVal color As Color)

        Dim brocha As New SolidBrush(color)

 

        GraficoBuffer.FillRectangle(brocha, x, y, ancho, alto)

    End Sub

 

    Public Overloads Sub DibujarRectangulo(ByVal x As Integer, ByVal y As Integer, ByVal ancho As Integer, ByVal alto As Integer, ByVal color1 As Color, ByVal color2 As Color)

        'Dim brocha As New SolidBrush(color)

        Dim rectangulo As New Rectangle(x, y, ancho, alto)

        Dim brocha As New Drawing2D.LinearGradientBrush(rectangulo, color1, color2, Drawing2D.LinearGradientMode.Vertical)

 

        GraficoBuffer.FillRectangle(brocha, rectangulo)

    End Sub

 

    Public Sub DibujarLinea(ByVal x1 As Integer, ByVal y1 As Integer, ByVal x2 As Integer, ByVal y2 As Integer, ByVal grosor As Integer, ByVal color As Color)

        Dim lapiz As New Pen(color, grosor + 10)

 

        GraficoBuffer.DrawLine(lapiz, x1, y1, x2, y2)

    End Sub

 

    Public Sub Escribir(ByVal Texto As String, ByVal x As Integer, ByVal y As Integer, ByVal tamano As Integer, ByVal color As Color)

        Dim fuente As New Font("Arial", tamano, GraphicsUnit.Pixel)

        Dim brocha As New SolidBrush(color)

 

        GraficoBuffer.DrawString(Texto, fuente, brocha, x, y)

    End Sub

 

 

            Una vez se hayamos realizado desde la clase cliente todos los dibujos sobre nuestra clase grafico deberemos invocar al método actualizar, que será el encargado de volcar todo lo dibujado en el formulario o control.

 

    Public Sub Actualizar()

        Grafico.DrawImage(ImgBuffer, 0, 0)

    End Sub

  

 

Finalmente, haremos que la clase implemente la interfaz IDisposable con el objeto de poder liberar los recursos que utiliza.

 

Public Class Grafico

    Implements IDisposable

 

Esta interfaz está compuesta únicamente por un método: IDisposable.Dispose, que tendremos que implementar:

 

    Public Sub Dispose() Implements IDisposable.Dispose

        Grafico.Dispose()

        GraficoBuffer.Dispose()

        ImgBuffer.Dispose()

        Grafico = Nothing

        GraficoBuffer = Nothing

        ImgBuffer = Nothing

    End Sub

 

 

            Con lo que liberamos los recursos que hemos utilizado (un bitmap y dos graphics)

 

PROBANDO LA CLASE:

 

            Ahora vamos a probar la clase, para ello desarrollaremos un pequeño proyecto que dibuje en un formulario varias figuras y texto y que incluya alguna imagen en movimiento.

Nos serviremos del formulario que tenemos (llamado Form1) e incluiremos un módulo al que llamaremos main.

            Dentro de este módulo declararemos las siguientes variables privadas:

 

    Dim WithEvents frm As Form1

    Dim salir As Boolean = False

    Dim i As Integer

    Dim j As Integer

 

    Dim oGrafico As Grafico

    Dim fps As FPS

 

La clase FPS (que se explicará más adelante) es la encargada de calcular el número de frames por segundo que dibuja la aplicación.

Crearemos también el procedimiento inicial del proyecto (main) compuesto de tres partes. La primera donde declaramos e inicializamos todas las variables y objetos; el bucle en el que moveremos las imágenes y dibujaremos todas las figuras así como el texto en el formulario; y finalmente la parte en la que liberamos los recursos consumidos por el objeto oGrafico

 

    Public Sub main()

        Dim basura As Integer

 

        frm = New Form1

        frm.Show()

        oGrafico = New Grafico(frm)

 

        fps = New FPS

        i = 11

        v = 3

 

        While Not salir

            If i > 200 Or i < 10 Then

                v = -1 * v

            End If

            i = i + v

 

            dibujar()

            Application.DoEvents()

        End While

 

        oGrafico.Dispose()

    End Sub

 

           

            En el procedimiento dibujar simplemente llamamos a los diferentes métodos que expone nuestra clase grafico para dibujar y escribir en el formulario y finalmente llamamos al método Actualizar para que estas acciones queden reflejadas en el formulario.

 

    Private Sub dibujar()

 

        fps.AnadirFrame()

 

        oGrafico.Borrar()

        oGrafico.DibujarImagen(10, 10, "dibujo.bmp")

        oGrafico.DibujarImagen(10, 40, 50, 50, "dibujo.bmp")

        oGrafico.DibujarCirculo(i, 50, 10, Color.Black)

        oGrafico.DibujarCirculo(50, 70, 20, Color.Green)

        oGrafico.DibujarCirculo(i + 50, 270, 10, Color.Red)

        oGrafico.DibujarLinea(10, 250, 200, 250, 2, Color.Violet)

        oGrafico.DibujarRectangulo(30, 100, 100, 30, Color.Turquoise)

        oGrafico.DibujarRectangulo(30, 150, 100, 30, Color.Turquoise, Color.Black)

        oGrafico.Escribir("Pulse espacio para salir", 10, 10, 12, Color.Red)

        oGrafico.Escribir("Frames por Segundo: " & fps.FPSActual.ToString, 10, 25, 12, Color.Red)

        oGrafico.Actualizar()

    End Sub

 

 

Cuando declaramos el formulario lo declaramos withEvents para poder manejar desde este módulo los eventos que levante. Esto nos será util para poner fin a la ejecución del bucle y salir de la aplicación, bien sea porque se ha pulsado espacio o porque se ha intentado cerrar el formulario:

 

    Private Sub frm_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles frm.KeyDown

        If e.KeyCode = Keys.Space Then

            salir = True

        End If

    End Sub

 

    Private Sub frm_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles frm.Closing

        e.Cancel = True

        salir = True

    End Sub

 

 

            El código de la clase FPS que utilizamos en el módulo para calcular los frames  por segundo es el siguiente:

 

Option Explicit On

Option Strict On

 

Public Class FPS

 

    Dim mFPSActual As Long

    Dim mTicksAnterior As Long

    Dim mFPSSuma As Long

 

    Public ReadOnly Property FPSActual() As Long

        Get

            Return mFPSActual

        End Get

    End Property

 

    Public Sub New()

        mTicksAnterior = Now.Ticks

        mFPSActual = 0

        mFPSSuma = 0

    End Sub

       

    Public Sub AnadirFrame()

        '1.000.000.000 = 1 seg

        'un tick = 100 nanosegundos -> 1 seg = 10.000.000 ticks

        If (Now.Ticks() - mTicksAnterior) >= 10000000 Then

            mFPSActual = mFPSSuma

            mFPSSuma = 0

            mTicksAnterior = Now.Ticks()

        End If

        mFPSSuma = mFPSSuma + 1

    End Sub

End Class

   

Resultado de la ejecución del proyecto.

 


ir al índice

Fichero con el código de ejemplo: bnlbnl_Grafico - Tamaño 8 KB