17 El GDI
Hagamos una pausa en el tema de controles para hablar un poco del GDI.
El GDI (Graphics Device Interface), es decir, el interfaz de dispositivo gráfico contiene todas las funciones y estructuras necesarias que nos permiten comunicar nuestras aplicaciones con cualquier dispositivo gráfico de salida conectado a nuestro ordenador: pantalla, impresora, plotter, etc, sin preocuparnos de cómo espera el dispositivo recibir las ordenes para mostrar gráficos.
Las aplicaciones GUI usan el GDI para dirigir las salidas gráficas a lo que se conoce como un Contexto de Dispositivo, abreviado como DC. El DC permite a la aplicación conocer los parámetros concretos del dispositivo para el que fue creado. Así que cada DC se crea para un dispositivo concreto, ya sea una pantalla, una impresora o cualquier otro dispositivo de salida. El GDI es el que se comunica por un lado con la aplicación, y por otro con los drivers de cada dispositivo.
De este modo podemos usar las mismas funciones, o en el caso de wxWidgets, las mismas clases, para comunicarnos con cualquier dispositivo gráfico.
El DC
Lo primero que necesitamos para mostrar cualquier gráfico es conseguir un DC para el dispositivo que vayamos a utilizar.
Todos los DCs de wxWidgets parten de la clase base wxDC. Pero, como sucede con muchas clases base, se trata de una clase abstracta, que no se puede usar directamente. Tendremos que usar la clase derivada adecuada para el dispositivo de salida deseado. Por ejemplo, para dibujar en pantalla usaremos una de las clases wxScreenDC, wxWindowDC, wxClientDC o wxPaintDC. Para la impresora una clase wxPostScriptDC o wxPrinterDC, etc.
Sin embargo, la mayor parte de los métodos para crear gráficos: texto, líneas, figuras, etc, están definidos en la clase base, y las derivadas sólo añaden o modifican algunos métodos. Por ejemplo, muchas clases, como wxWindowDC, wxClientDC o wxPaintDC sólo redefinen el constructor.
La clase wxDC contiene muchos métodos, de los que en este capítulo veremos algunos.
DCs de pantalla
Par ilustrar el funcionamiento del GDI nos centraremos en la pantalla, ya que se trata del periférico de salida más común, y el más completo en lo que se refiere a capacidades como colores, fuentes, etc. Además, resultará más sencillo ilustrar los ejemplos si usamos como dispositivo de salida ventanas o cuadros de diálogo.
Para conseguir un DC de pantalla, como ya comentamos antes, tenemos varias opciones, cada una con sus características particulares:
- wxScreenDC: originalmente destinada a trazar gráficos en pantalla completa, aunque en las últimas versiones de wxWidgets está desaconsejado su uso, y no está garantizado que funcione.
- wxWindowDC: originalmente usada para trazar gráficos en toda la ventana, incluyendo bordes, menús, etc. Al igual que la anterior, en las últimas versiones de la librería se desaconseja su uso, y no se garantiza su funcionamiento.
- wxClientDC: destinado a obtener información sobre características gráficas de una ventana. Por ejemplo, se suele usar para establecer las dimensiones de una ventana en función del tamaño de un texto. Aunque en algunas versiones de la librería para ciertos sistemas se puede usar para trazar gráficos en el área de cliente de la ventana, no funciona con todas, y se desaconseja usarla para eso.
- wxPaintDC: se usa para trazar gráficos en el área de cliente de una ventana como respuesta a un evento EVT_PAINT. Este es el tipo de DC que usaremos para dibujar en pantalla.
Evento EVT_PAINT
La forma de actualizar el contenido de una ventana, sin contar los controles, que se actualizan automáticamente, es procesar los eventos EVT_PAINT.
Estos eventos se generan cada vez que el contenido de una ventana deba ser actualizado. Esto puede suceder porque una parte o toda la ventana haya sido tapada por otras ventanas, y posteriormente sea visible, o porque se haya maximizado o restaurado, etc. También puede ser generado por la propia aplicación al invalidar parte o la totalidad del área de cliente para asegurarse de que la información mostrada está actualizada.
Como como el resto de los eventos, deberemos declararlo en la tabla de eventos, y asignarle un método de la ventana para que lo procese:
BEGIN_EVENT_TABLE(miFrame, wxFrame) EVT_CLOSE(miFrame::OnClose) EVT_PAINT(miFrame::OnPaint) ... END_EVENT_TABLE()
El método recibirá una referencia a un objeto de tipo wxPaintEvent, que contiene información relativa al propio evento, aunque generalmente no necesitaremos usarla.
El método debe crear un DC local de la clase wxPaintDC, que se usará para dibujar en la ventana, y se debe crear el objeto aunque posteriormente no se use.
void miFrame::OnPaint(wxPaintEvent& event) { wxPaintDC dc(this); ... }
Plumas
Antes de dibujar cualquier línea o figura deberemos seleccionar ciertas características, como el color, entre otras. Las líneas si dibujan usando "plumas" o "pens", y las figuras se rellenan usando un "pincel" o "brush".
Las plumas se encapsulan en objetos de la clase wxPen, y los pinceles en objetos de la clase wxBrush.

Para una pluma podemos definir las siguientes propiedades:
- Color.
- Anchura.
- Estilo.
- Forma de los extremos de línea.
- Forma de las uniones entre líneas.
- Calidad.
Las características como color y anchura son evidentes, podemos crear plumas de diferentes colores y distintas anchuras en pixels.
El estilo afecta al aspecto de las líneas. Por ejemplo, un estilo sólido, wxPENSTYLE_SOLID, dibujará líneas sólidas, un estilo de puntos, wxPENSTYLE_DOT, dibujará líneas de puntos, etc.

Cuando la anchura sea de más de un pixel existen diferentes formas de dibujar los extremos de las líneas. Actualmente hay tres opciones válidas: wxCAP_ROUND, para terminar las líneas con forma redondeada, wxCAP_PROJECTING, la línea se alarga en cada extremo la mitad de su anchura y wxCAP_BUTT, no se añaden terminadores.

De forma análoga, cuando se unen distintas líneas, también existen formas diferentes de hacerlo. Actualmente hay tres opciones válidas: wxJOIN_BEVEL, uniones biseladas; wxJOIN_MITER, uniones en punta y wxJOIN_ROUND, uniones redondeadas.
En cuanto a la calidad, podemos elegir entre wxPEN_QUALITY_LOW, de peor calidad, pero más rápido o wxPEN_QUALITY_HIGH, mejor calidad pero más lento.
La mayor parte de los métodos de la clase wxPen corresponden a la interfaz para consultar o modificar esos parámetros. También disponemos de varios constructores diferentes.
Para definir colores usaremos la clase wxColour, que dispone de varios constructores diferentes y de métodos para obtener y modificar componentes:
wxPen pluma(wxColour(240,50, 240), 20); pluma.SetCap(wxCAP_BUTT); pluma.SetJoin(wxJOIN_BEVEL); dc.SetPen(pluma);
El estilo wxPENSTYLE_USER_DASH permite definir un punteado de línea a medida. Para ello hay que definir un array de valores de tipo wxDash, la definición de este tipo depende de la plataforma. En ese array se usan números que indican las longitudes de línea y espacio entre líneas alternativamente. Por ejemplo:
wxDash dash[] = {0, 4, 0, 2, 4, 4, 4, 2}; ... pluma.SetDashes(8, dash); pluma.SetStyle(wxPENSTYLE_USER_DASH); dc.SetPen(pluma);
Estos valores defienen un patrón que consta de una línea corta, un espacio largo, otra línea corta, un espacio medio, una línea larga, un espacio largo, otra línea larga y un espacio medio. El patrón se repite, y las longitudes dependen de los valores indicados y de la anchura de línea.
El estilo wxPENSTYLE_STIPPLE usa un mapa de bits para el patrón de la línea. Es necesario asignar el mapa de bits a utilizar y activar el estilo:
#include "stipple.xpm" ... wxBitmap bmp(stipple); ... pluma.SetWidth(7); pluma.SetStipple(bmp); pluma.SetStyle(wxPENSTYLE_STIPPLE); dc.SetPen(pluma);
La clase wxDC dispone de muchos métodos para trazar líneas usando pinceles, como DrawArc, DrawCircle, DrawEllipse, DrawEllipticArc, DrawLine, DrawLines, DrawPoint o DrawSpile. Además, se usa la pluma para dibujar el contorno de figuras como DrawPoligon, DrawPolyPolygon, DrawRectangle o DrawRoundRectangle.
Pinceles
Los pinceles en objetos de la clase wxBrush.
Para un pincel podemos definir las siguientes propiedades: Color y Estilo.
Disponemos de varios constructores. El constructor por defecto que sólo crea un pincel nulo.
Otro de los constructores es el constructor copia.
El resto nos permiten crear un pincel con un color y estilo concreto, o a partir de un mapa de bits.

En cuanto a los estilos, se puede usar el estilo sólido, que crea un pincel de un único color. Los estilos STIPPLE permiten usar un mapa de bits que se usará para rellenar áreas como mosaico, con diferentes opciones de máscara.
Los estilos HATCH permiten seleccionar diferentes estilos de sombreado: líneas horizontales, verticales, inclinadas, etc.
El resto de métodos de la clase wxBrush forman la interfaz para acceder o modificar propiedades del pincel después de creado. Por ejemplo, para cambiar el color se usa el método SetColour, o para cambiar el estilo, SetStyle, etc.
wxBrush pincel(wxColour(50, 50, 250)); pincel.SetStyle(wxBRUSHSTYLE_SOLID); dc.SetBrush(pincel); dc.DrawRectangle(wxPoint(20,20), wxSize(300,20)); pincel.SetStyle(wxBRUSHSTYLE_TRANSPARENT); dc.SetBrush(pincel); dc.DrawRectangle(wxPoint(20,45), wxSize(300,20));
Los métodos de wxDC que usan pinceles para rellenar figuras como DrawPoligon, DrawPolyPolygon, DrawRectangle, DrawRoundRectangle o FloodFill.
Mapas de bits
Un mapa de bits contiene la información de un conjunto de pixels, que generalmente representan una imagen rectangular. La clase wxBitmap contiene tanto la información sobre esos pixels como sobre la profundidad de color (número de bits usados para codificar cada pixel), el tamaño (anchura y altura), la paleta de colores (dependiendo del modo de codificación), máscara, etc.
Además, wxWidgets dispone de otra clase para manejar imágenes. La clase wxImage, que no es un objeto GDI, encapsula imágenes, pero de forma independiente de la plataforma, por lo que resulta más conveniente a la hora de crear aplicaciones multiplataforma.
Al no tratarse de un objeto GDI no será posible usar estos objetos para mostrarlos directamente en un DC, sino que habrá que convertirlos previamente a un objeto wxBitmap, usando el constructor correspondiente.
Las ventajas de wxImage son muchas:
- Permite la manipulación de la imagen, ya que proporciona acceso a los pixels de forma individual. Se pueden escalar, redimensionar, recortar, filtrar, etc.
- Tiene soporte para el canal alfa.
- Dispone de manejadores de imagen para multitud de formatos diferentes: BMP, PNG, JPEG, GIF, PCX, PNM, TIFF, TGA, IFF, ICO, CUR, ANI.
La clase wxDC nos proporciona tres métodos para mostrar mapas de bits.
El más sencillo es el método DrawBitmap, que sencillamente muestra un mapa de bits en las coordenadas indicadas:
wxBitmap bmp; ... dc.DrawBitmap(bmp, 30, 30);

Los otros dos métodos, Blit y StretchBlit, no usan directamente un mapa de bits, sino que copian un DC en otro DC. Para mostrar un mapa de bits usando estas funciones primero hay que crear un DC con la imagen correspondiente al mapa de bits. Para eso usaremos un DC de memoria, un wxMemoryDC, compatible con el DC de pantalla de destino.
Los DC en memoria se usan exclusivamente para contener mapas de bits, que posteriormente se mostrarán en pantalla.
Empezaremos por crear un DC de memoria con las mismas características que el DC de destino, y luego seleccionaremos el mapa de bits en ese DC, y finalmente usaremos una de las funciones de transferencia de DCs:
wxDC dc; // dc es el DC de destino, generalmente un wxPaintDC wxMemoryDC memDC(&dc); wxBitmap bmp; ... memDC.SelectObject(bmp); ... dc.Blit(wxPoint(20,20), bmp.GetSize(), &memDC, wxPoint(0,0));
Esto puede parecer innecesariamente enrevesado, pero tiene su explicación.
La ventaja de estos métodos es que no se limitan a copiar un mapa de bits, sino que nos permiten copiar y/o escalar parte del mapa de bits, así como aplicar máscaras y usar diferentes funciones de rasterización: copia, XOR, ADN, OR, NAND, inversión, etc. Puedes consultar todas las posibles operaciones en modos de operación raster.
Blit no escala la imagen: a cada pixel de origen le corresponde un pixel de destino. Por contra StretchBlit si permite escalado, pudiendo ampliar o reducir el tamaño original del fragmento del DC de origen.
dc.StretchBlit(wxPoint(20,20), wxSize(200,200), &memDC, wxPoint(0,0), wxSize(100, 100)); dc.StretchBlit(wxPoint(230,20), wxSize(50,50), &memDC, wxPoint(0,0), bmp.GetSize());
Iconos
Los iconos, que se encapsulan mediante la clase wxIcon, no dejan de ser un tipo especial de mapa de bits. wxDC nos proporciona un método para mostrar un icono en las coordenadas especificadas.
El método DrawIcon toma como parámetros un objeto icono y unas coordenadas:
wxIcon ic(icono_xpm); dc.DrawIcon(ic, wxPoint(20,20));

Fuentes
A la hora de mostrar texto disponemos de multitud de opciones, como el color del texto y el fondo, el tipo, tamaño y estilo de los caracteres, etc.
Lo primero que necesitaremos es crear o seleccionar una fuente de caracteres.
Las fuentes se encapsulan en la clase wxFont. Para crear una fuente usaremos uno de los constructores. La documentación recomienda usar la sobrecarga que usa la clase wxFontInfo, ya que resulta un código más legible:
wxFont fuente(wxFontInfo(20).Bold().Italic());
La clase wxFontInfo contiene métodos que nos permiten especificar todas las características de la fuente. Además, como cada método devuelve una referencia al propio objeto, es posible encadenarlos. De este modo es sencillo elegir o ver todas las características de una fuente, de una forma bastante directa.
Estos métodos incluyen Blold, Family, Italic, Light, Strikethrough o Underlined. El tamaño de la fuente se establece en el constructor. En el ejemplo anterior, el tamaño de la fuente es de 20 puntos, en negrita y cursiva.
Otros métodos permiten especificar la codificación a usar, Encoding, el estilo, Style, usando una de las constantes wxFontStyle o el peso, Weight, bien con un valor entre 1 y 1000, o usando una de las constantes wxFontWeight.
En el curso del API de Windows se describen las características de las fuentes, que tienen el mismo significado que en wxWidgets.
Una vez creada la fuente, tendremos que seleccionarla en el DC para que se use en las siguientes salidas de texto, usando el método SetFont.
Finalmente, para mostrar texto en la pantalla usaremos uno de los métodos disponibles DrawText, que requiere como parámetros la cadena a mostrar y las coordenadas, o DrawRotatedText, que requiere un tercer parámetro que indica el ángulo que se quiere rotar el texto, en grados. El ángulo son positivos en el sentido contrario a las agujas del reloj y 0º corresponden al texto horizontal normal.
dc.SetFont(fuente2); dc.DrawRotatedText(_T("Con Clase"), wxPoint(20,220), 45);
Para el color, si no se especifica lo contrario, se usa el color por defecto. Esto se puede modificar con el método SetTextForeground.
Además existen dos modos que afectan el fondo. El modo transparente conserva el fondo anterior al trazado del texto, y es el modo por defecto. El modo sólido usa un segundo color para el fondo, eliminando cualquier valor anterior. El modo se puede modificar mediante el método SetBackgroundMode, indicando como parámetro uno de los valores wxBRUSHSTYLE_SOLID o wxBRUSHSTYLE_TRANSPARENT.
Si se selecciona el modo sólido, podemos modificar el color del fondo mediante el método SetTextBackground.
dc.SetFont(fuente); dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID); dc.SetTextForeground("#20ff20"); dc.SetTextBackground("#800505"); dc.DrawText(_T("Con Clase"), wxPoint(20,250));
Puede ser útil calcular el tamaño necesario para mostrar un texto antes de mostrarlo realmente. Para ello disponemos de los métodos: GetTextExtent, GetMultiLineTextExtent o GetPartialTextExtents.
Otros objetos del GDI
Los objetos del GDI derivan de la clase wxGDIObject. Además de los que hemos visto, también incluyen otros como:
- Las regiones, wxRegion, que se usan para operaciones de recorte (cliping) y para invalidar o enmascarar zonas de la pantalla.
- El cursor, wxCursor, que permite modificar el cursor del ratón para indicar el tipo de acciones que el usuario puede realizar.
- La paleta, wxPalette, que se usa en mapas de bits con un número pequeño de colores. Actualmente prácticamente no se usa, y sólo se mantiene por compatibilidad y para ciertos formatos de fichero de imagen.
Otros tipos de DC
wxDC es la clase base de la que se derivan muchos tipos de contextos de dispositivo diferentes, cada uno con características propias y orientados a distintos dispositivos o tareas.
Ya hemos visto los destinados a manejar la pantalla, y el wxMemoryDC, que se usa para mostrar mapas de bits. Pero hay más:
DCs con buffer
Existe un grupo de contextos de dispositivo que utilizan el concepto de doble buffer. Esto evita el parpadeo de la pantalla cuando el DC se actualizar muy a menudo.
Todos ellos derivan de wxMemoryDC, es decir, se dibuja en memoria, y justo antes de destruir el DC se copia en el wxDC asociado. Todo esto es automático y transparente para el programador.
Tenemos los siguientes DCs:
- wxBufferedDC, que generalmente se asocia a un wxClientDC, por lo que normalmente no tendremos que usarlos.
- wxBufferedPaintDC, que se puede usar directamente donde normalmente usaríamos un wxPaintDC. Sin embargo tendremos que tener en cuenta algunos comportamientos diferentes. Por ejemplo, el contenido previo de la ventana no se borra cada vez que se actualice el DC, de modo que si queremos que el nuevo contenido sustituya al anterior deberemos borrar el DC usando el método Clear.
- wxAutoBufferedPaintDC, funciona igual que el anterior, salvo que la plataforma ya proporcione una forma nativa de doble buffer, en cuyo caso se comporta igual que wxPaintDC. Por lo tanto parece lógico usar esta clase en lugar de la anterior.
Cuando se usa uno de estas dos últimos DCs es necesario establecer el estilo del fondo de la ventana, normalmente en el constructor de la ventana:
SetBackgroundStyle(wxBG_STYLE_PAINT);
Después sólo hay que usar uno de estos objetos donde normalmente usaríamos un wxPaintDC:
void wxMiFrame::OnPaint(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(this); dc.Clear(); ...
DC de impresora
El DC wxPrinterDC está orientado a mostrar gráficos y texto en una impresora del sistema. En el futuro dedicaremos un capítulo a este tema.
DCs de ficheros
Existen clases de contextos de dispositivos específicos para crear ciertos ficheros de gráficos vectoriales:
- wxSVGFileDC: para la creación de ficheros SVG.
- wxMetafileDC: para la creación de ficheros metafile de Windows.
- wxPostScriptDC: para la creación de ficheros PostScript.
Pueden ser útiles cuando la salida de nuestra aplicación deba exportarse a otras aplicaciones que requieran gráficos vectoriales. Por ejemplo, los ficheros SVG se usan frecuentemente en aplicaciones y páginas web, y los ficheros PostScript en impresoras.
El mismo procedimiento que muestra un gráfico en pantalla cuando le pasamos un wxPaintDC, generará un fichero SVG cuando le pasemos un wxSVGFileDC.
void wx017Frame::TrataSVG(wxDC& dc) { DibujaSVG(dc); // Salida en pantalla wxSVGFileDC svgDC("dibujo.svg", 320, 240, 72, "cuadrados"); DibujaSVG(svgDC); // Salida en fichero } void wx017Frame::DibujaSVG(wxDC& dc) { wxPen pluma(wxColour(240,240,50), 20); wxPen fina(wxColour(0,0,0), 1); dc.SetPen(pluma); dc.DrawLine(wxPoint(20,20), wxPoint(150,20)); dc.DrawLine(wxPoint(150,20), wxPoint(150,150)); dc.DrawLine(wxPoint(150,150), wxPoint(20,150)); dc.DrawLine(wxPoint(20,150), wxPoint(20,20)); dc.SetPen(fina); dc.DrawLine(wxPoint(20,20), wxPoint(150,20)); dc.DrawLine(wxPoint(150,20), wxPoint(150,150)); dc.DrawLine(wxPoint(150,150), wxPoint(20,150)); dc.DrawLine(wxPoint(20,150), wxPoint(20,20)); }
DC de espejo
La clase wxMirrorDC permite reutilizar un dispositivo de contexto cuando se necesiten imágenes reflejadas, sin necesidad de redibujar todos los gráficos, ya que facilita la inversión de ejes.
DC de contexto gráfico
Por último, la clase wxGCDC encapsula un contexto gráfico, que tiene algunas ventajas sobre un DC normal, aunque no soporta todas sus funciones. También dedicaremos a este tema un capítulo en el futuro.
Ejemplo 17
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 17 | wx017.zip | 2025-02-16 | 27803 bytes | 5 |