Compartir a través de


Cambios en el modelo de programación

En las siguientes secciones se describen varios aspectos en los que la programación con GDI+ es distinta de la programación con GDI.

Contextos de dispositivo, identificadores y objetos gráficos

Si ya ha escrito programas con GDI (la interfaz de dispositivo gráfico incluida en versiones anteriores de Windows), estará familiarizado con la idea de un contexto de dispositivo. Un contexto de dispositivo es una estructura que Windows utiliza para almacenar información sobre las características de un dispositivo de pantalla específico y los atributos que especifican la forma en que se van a dibujar elementos en ese dispositivo. Un contexto de dispositivo para una pantalla de vídeo se asocia también a una ventana específica de la pantalla. Primero se obtiene un identificador de un contexto de dispositivo (HDC), y después se pasa ese identificador como argumento de las funciones GDI que realmente realizan el dibujo. También se pasa el identificador como argumento de las funciones GDI que obtienen o establecen los atributos del contexto de dispositivo.

Con GDI+, no es necesario utilizar identificadores o contextos de dispositivo. En lugar de ello, simplemente se crea un objeto Graphics y después se llama a sus métodos en el estilo orientado a objetos convencional: myGraphicsObject.DrawLine(parámetros). El objeto Graphics es la base de GDI+, del mismo modo que el contexto de dispositivo es la base de GDI. El contexto de dispositivo y el objeto Graphics desempeñan tareas similares, pero existen diferencias fundamentales entre el modelo de programación basado en identificadores utilizado con contextos de dispositivo (GDI) y el modelo orientado a objetos utilizado con objetos Graphics (GDI+).

El objeto Graphics, al igual que el contexto de dispositivo, se asocia a una ventana específica de la pantalla y tiene propiedades (por ejemplo, SmoothingMode y TextRenderingHint) que especifican el modo en que se van a dibujar elementos. Sin embargo, el objeto Graphics no está ligado a un lápiz, pincel, trazado, imagen o fuente, mientras que el contexto de dispositivo sí lo está. Por ejemplo, antes de que pueda utilizar un contexto de dispositivo para dibujar una línea, debe llamar a SelectObject para asociar un objeto pen al contexto de dispositivo. Esto se conoce como selección del lápiz en el contexto de dispositivo. Todas las líneas dibujadas en el contexto de dispositivo utilizarán este lápiz hasta que se seleccione un lápiz diferente. En GDI+, se pasa un objeto Pen como argumento del método DrawLine de la clase Graphics. Se puede utilizar un objeto Pen diferente en cada una de las series de llamadas al método DrawLine, sin tener que asociar un objeto Pen determinado a un objeto Graphics.

Dos maneras de dibujar una línea

Cada uno de los ejemplos que se muestran a continuación dibuja una línea roja de ancho 3 desde la ubicación (20, 10) hasta la ubicación (200, 100). En el primer ejemplo se llama a GDI y en el segundo ejemplo se llama a GDI+ a través de la interfaz de clases administradas.

Dibujar una línea con GDI

Para dibujar una línea con GDI, son necesarios dos objetos: un contexto de dispositivo y un lápiz. Para obtener un identificador de un contexto de dispositivo, hay que llamar a BeginPaint, y para obtener un identificador de un lápiz, hay que llamar a CreatePen. A continuación, se llama a SelectObject para seleccionar el lápiz en el contexto de dispositivo. Se establece la posición de lápiz en (20, 10) llamando a MoveToEx y, después, se dibuja una línea desde dicha posición de lápiz hasta (200, 100) llamando a LineTo. Tenga en cuenta que tanto MoveToEx como LineTo reciben hdc (el identificador del contexto de dispositivo) como argumento.

HDC          hdc;
PAINTSTRUCT  ps;
HPEN         hPen;
...
hdc = BeginPaint(hWnd, &ps);
   hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
   SelectObject(hdc, hPen);
   MoveToEx(hdc, 20, 10, NULL);
   LineTo(hdc, 200, 100);
EndPaint(hWnd, &ps);

Dibujar una línea con GDI+ y la interfaz de clases administradas

Los ejemplos siguientes están escritos en C# y Visual Basic, pero podrían haberse escrito en cualquier lenguaje capaz de generar código administrado. Para dibujar una línea con GDI+ y la interfaz de clases administradas, se necesita un objeto Graphics y un objeto Pen. Una forma de obtener una referencia a un objeto Graphics es por medio del método OnPaint de un formulario. El único parámetro del método OnPaint es una estructura de tipo PaintEventArgs, que tiene un objeto Graphics como uno de sus miembros.

El hecho de dibujar una línea implica llamar al método DrawLine de la clase Graphics. El primer parámetro del método DrawLine es un objeto Pen. Este es un esquema más sencillo y flexible que la técnica (selección de un lápiz en un contexto de dispositivo) que se muestra en el ejemplo anterior de GDI.

Class PlainForm
   Inherits Form
   
   Protected Overrides Sub OnPaint(e As PaintEventArgs)
      Dim myPen As New Pen(Color.Red, 3)
      Dim myGraphics As Graphics = e.Graphics
      myGraphics.DrawLine(myPen, 20, 10, 200, 100)
   End Sub 'OnPaint
End Class 'PlainForm
[C#]
class PlainForm : Form
{
   protected override void OnPaint(PaintEventArgs e)
   {
      Pen myPen = new Pen(Color.Red, 3);
      Graphics myGraphics = e.Graphics;
      myGraphics.DrawLine(myPen, 20, 10, 200, 100);
   }
}

Lápices, pinceles, trazados, imágenes y fuentes como parámetros

Los ejemplos anteriores muestran que los objetos Pen pueden crearse y mantenerse independientemente del objeto Graphics, que proporciona los métodos de dibujo. Los objetos Brush, GraphicsPath, Image y Font también pueden crearse y mantenerse independientemente del objeto Graphics. Muchos de los métodos de dibujo que proporciona la clase Graphics reciben un objeto Brush, GraphicsPath, Image o Font como argumento. Por ejemplo, un objeto Brush se pasa como argumento al método FillRectangle, y un objeto GraphicsPath se pasa como argumento al método DrawPath. De forma similar, los objetos Image y Font se pasan a los métodos DrawImage y DrawString. Esto ocurre en contraste con GDI, donde se selecciona un pincel, trazado, imagen o fuente en el contexto de dispositivo y después se pasa un identificador al contexto de dispositivo como argumento de una función de dibujo.

Sobrecarga de métodos

Muchos de los métodos de GDI+ se sobrecargan; es decir, varios métodos comparten el mismo nombre pero tienen listas de parámetros distintas. Por ejemplo, el método DrawLine de la clase Graphics se presenta de las siguientes formas:

Overloads Sub DrawLine( _
   pen As Pen, _
   x1 As Single, _
   y1 As Single, _
   x2 As Single, _
   y2 As Single)

End Sub 'DrawLine
   
Overloads Sub DrawLine( _
   pen As Pen, _
   pt1 As PointF, _
   pt2 As PointF)

End Sub 'DrawLine
   
Overloads Sub DrawLine( _
   pen As Pen, _
   x1 As Integer, _
   y1 As Integer, _
   x2 As Integer, _
   y2 As Integer)

End Sub 'DrawLine
   
Overloads Sub DrawLine( _
   pen As Pen, _
   pt1 As Point, _
   pt2 As Point)

End Sub 'DrawLine
[C#]
void DrawLine(
   Pen pen,
   float x1,
   float y1,
   float x2,
   float y2) {}

void DrawLine(
   Pen pen,
   PointF pt1,
   PointF pt2) {}

void DrawLine(
   Pen pen,
   int x1,
   int y1,
   int x2,
   int y2) {}

void DrawLine(
   Pen pen,
   Point pt1,
   Point pt2) {}

Las cuatro variaciones de DrawLine anteriores reciben un objeto Pen, las coordenadas del punto inicial y las coordenadas del punto final. Las dos primeras variaciones reciben las coordenadas como números de punto flotante, y las dos últimas reciben las coordenadas como enteros. La primera y tercera variaciones reciben las coordenadas como una lista de cuatro números independientes, mientras que la segunda y la cuarta reciben las coordenadas como un par de objetos Point (o PointF).

Sin noción de posición actual

Observe que en los métodos DrawLine mostrados anteriormente, el punto inicial y el punto final de la línea se reciben como argumentos. Esto es distinto a lo que ocurre con el esquema GDI, en el que se llama a MoveToEx(hdc, x1, y1, NULL) con objeto de establecer la posición actual del lápiz seguida de LineTo(hdc, x2, y2) para dibujar una línea que comience en (x1, y1) y termine en (x2, y2). GDI+, en conjunto, ha abandonado la noción de posición actual.

Métodos independientes para dibujar y rellenar

GDI+ es más flexible que GDI cuando se trata de dibujar los contornos y el relleno del interior de las formas. GDI dispone de una función Rectangle, que dibuja el contorno y rellena el interior de un rectángulo en un solo paso. El contorno se dibuja con el lápiz actualmente seleccionado, y el interior se rellena con el pincel actualmente seleccionado.

hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 100, 50, 200, 80);

GDI+ dispone de métodos independientes para dibujar el contorno y rellenar el interior de un rectángulo. El método DrawRectangle de la clase Graphics tiene un objeto Pen como uno de sus parámetros y el método FillRectangle tiene un objeto Brush como uno de sus parámetros.

Dim myHatchBrush As New HatchBrush( _
   HatchStyle.Cross, _ 
   Color.FromArgb(255, 0, 255, 0), _
   Color.FromArgb(255, 0, 0, 255))
Dim myPen As New Pen(Color.FromArgb(255, 255, 0, 0), 3)
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30)
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30)
[C#]
HatchBrush myHatchBrush = new HatchBrush(
   HatchStyle.Cross,
   Color.FromArgb(255, 0, 255, 0),
   Color.FromArgb(255, 0, 0, 255));
Pen myPen = new Pen(Color.FromArgb(255, 255, 0, 0), 3);
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30);
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);

Observe que los métodos FillRectangle y DrawRectangle, en GDI+, reciben argumentos que especifican el borde izquierdo, la parte superior, el ancho y alto del rectángulo. Esto ocurre en contraste con la función Rectangle de GDI, que toma argumentos que especifican el borde izquierdo, el borde derecho, la parte superior y parte inferior del rectángulo. Observe también que el método FromArgb de la clase Color, en GDI+, tiene cuatro parámetros. Los tres últimos parámetros son los valores habituales rojo, verde y azul; el primer parámetro es el valor alfa, que especifica hasta qué punto el color que se está dibujando se mezcla con el color de fondo.

Construir Regiones

GDI proporciona varias funciones para crear regiones: CreateRectRgn, CreateEllpticRgn, CreateRoundRectRgn, CreatePolygonRgn y CreatePolyPolygonRgn. Se podría esperar que la clase Region, en GDI+, tuviera constructores análogos que tomasen rectángulos, elipses, rectángulos redondeados y polígonos como argumentos, pero no es éste el caso. La clase Region, en GDI+, proporciona un constructor que recibe un objeto Rectangle y otro constructor que recibe un objeto GraphicsPath. Si se desea construir una región basada en una elipse, rectángulo redondeado o polígono, se puede hacer fácilmente; para ello, hay que crear un objeto GraphicsPath (que contenga una elipse, por ejemplo) y pasar un objeto GraphicsPath a un constructor Region.

GDI+ facilita la formación de regiones complejas mediante la combinación de formas y trazados. La clase Region dispone de métodos Union e Intersect que se pueden utilizar para aumentar una región existente con un trazado o con otra región. Una característica estupenda del esquema de GDI+ es que un objeto GraphicsPath no se destruye cuando se pasa como argumento de un constructor Region. (En GDI, se puede convertir un trazado en una región con la función PathToRegion, pero el trazado se destruye en el proceso.) Además, cuando se pasa un objeto GraphicsPath como argumento de un método Union o Intersect, dicho objeto no se destruye; de modo que un trazado determinado puede utilizarse como unidad de creación de varias regiones independientes. Esto último se muestra en el ejemplo siguiente. Supongamos que onePath es un objeto GraphicsPath (sencillo o complejo) que ya ha sido inicializado.

Dim region1 As New [Region](rect1)
Dim region2 As New [Region](rect2)
      
region1.Union(onePath)
region2.Intersect(onePath)
[C#]
Region region1 = new Region(rect1);
Region region2 = new Region(rect2);

region1.Union(onePath);
region2.Intersect(onePath);