Modifiche al modello di programmazione

Nelle sezioni seguenti vengono illustrate le differenze tra la programmazione con GDI+ e la programmazione con GDI.

Contesti di periferiche, handle e oggetti Graphics

Gli utenti che hanno utilizzato GDI (l'interfaccia di gestione periferiche grafiche inclusa in versioni precedenti di Windows) per la scrittura di programmi conoscono il concetto di contesto di periferica. Un contesto di periferica è una struttura utilizzata da Windows per memorizzare informazioni relative alle capacità di una determinata periferica di visualizzazione e gli attributi che specificano come tracciare degli elementi su tale periferica. Il contesto di periferica per uno schermo video è inoltre associato a una finestra particolare sullo schermo. È prima di tutto necessario ottenere un handle per un contesto di periferica (HDC, Handle to a Device Context) e passare tale handle come argomento alle funzioni GDI che tracciano effettivamente gli elementi. È inoltre necessario passare l'handle come argomento alle funzioni GDI che ottengono o impostano gli attributi del contesto di periferica.

L'utilizzo di handle o contesti di periferiche non è necessario in GDI+. È sufficiente infatti creare un oggetto Graphics e chiamarne i metodi utilizzando il consueto stile orientato ad oggetti, ovvero myGraphicsObject.DrawLine(parametri). L'oggetto Graphics è il fondamento di GDI+, proprio come il contesto di periferica è alla base di GDI. Il contesto di periferica e l'oggetto Graphics svolgono ruoli simili, ma vi sono differenze fondamentali tra il modello di programmazione basato su handle utilizzato con i contesti di periferica (GDI) e il modello orientato ad oggetti utilizzato con gli oggetti Graphics (GDI+).

L'oggetto Graphics, come il contesto di periferica, è associato a una particolare finestra sullo schermo e dispone di proprietà (ad esempio SmoothingMode e TextRenderingHint) che consentono di specificare la modalità in cui è necessario tracciare gli elementi. A differenza del contesto di periferica, l'oggetto Graphics tuttavia non è associato a una penna, un pennello, un percorso, un'immagine o un tipo di carattere. Prima di utilizzare un contesto di periferica per tracciare una linea ad esempio, è necessario chiamare SelectObject per associare una penna al contesto di periferica. Questa operazione viene definita come selezione della penna nel contesto di periferica. Tale penna verrà utilizzata da tutte le linee tracciate nel contesto di periferica, fino a quando non si seleziona un'altra penna. In GDI+ l'oggetto Pen viene passato come argomento al metodo DrawLine della classe Graphics. È possibile utilizzare un oggetto Pen diverso in ogni chiamata di una serie di chiamate DrawLine, senza che sia necessario associare un oggetto Pen specifico a un oggetto Graphics.

Due modalità per tracciare una linea

Gli esempi riportati di seguito consentono di tracciare una linea rossa di spessore 3 dalla posizione (20, 10) alla posizione (200, 100). Nel primo esempio viene chiamato GDI e nel secondo esempio viene chiamato GDI+ tramite l'interfaccia di classe gestita.

Tracciare una linea con GDI

Per tracciare una linea con GDI sono necessari due oggetti: un contesto di periferica e una penna. Si ottiene un handle per un contesto di periferica chiamando BeginPaint e un handle per una penna chiamando CreatePen. Si chiama quindi SelectObject per selezionare la penna nel contesto di periferica. Si imposta la posizione della penna su (20, 10) chiamando MoveToEx, quindi si traccia una linea da quella posizione della penna alla posizione (200, 100) chiamando LineTo. Si noti che sia MoveToEx che LineTo ricevono hdc (l'handle per il contesto di periferica) come argomento.

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);

Tracciare una linea con GDI+ e l'interfaccia di classe gestita

Gli esempi riportati di seguito sono scritti in C# e Visual Basic, ma sarebbe stato possibile scriverli utilizzando qualunque linguaggio in grado di creare codice gestito. Per tracciare una linea con GDI+ e l'interfaccia di classe gestita, sono necessari un oggetto Graphics e un oggetto Pen. Per ottenere un riferimento a un oggetto Graphics, è possibile utilizzare il metodo OnPaint di un form. L'unico parametro del metodo OnPaint è una struttura di tipo PaintEventArgs, tra i cui membri è incluso l'oggetto Graphics.

Tracciare una linea implica la chiamata del metodo DrawLine della classe Graphics. Il primo parametro del metodo DrawLine è un oggetto Pen. Questo schema risulta più semplice e flessibile della tecnica (selezione di una penna in un contesto di periferica) illustrata nell'esempio precedente relativo a 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);
   }
}

Penne, pennelli, percorsi, immagini e tipi di carattere come parametri

Negli esempi precedenti viene illustrato come sia possibile creare e mantenere degli oggetti Pen indipendentemente dall'oggetto Graphics, che fornisce i metodi per tracciare gli elementi. È inoltre possibile creare e mantenere degli oggetti Brush, GraphicsPath, Image e Font indipendentemente dall'oggetto Graphics. Molti dei metodi di disegno forniti dalla classe Graphics ricevono un oggetto Brush, GraphicsPath, Image o Font come argomento. Un oggetto Brush ad esempio viene passato come argomento al metodo FillRectangle e un oggetto GraphicsPath viene passato come argomento al metodo DrawPath. Analogamente, gli oggetti Image e Font vengono passati ai metodi DrawImage e DrawString. Questo procedimento si differenzia da quello di GDI, in cui si seleziona un pennello, un percorso, un'immagine o un tipo di carattere nel contesto di periferica, quindi si passa un handle al contesto di periferica come argomento per una funzione di disegno.

Overload dei metodi

Molti dei metodi di GDI+ sono in overload, ovvero svariati metodi condividono lo stesso nome ma dispongono di elenchi di parametri diversi. Il metodo DrawLine della classe Graphics ad esempio è disponibile nelle seguenti forme:

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) {}

Tutte e quattro le variazioni riportate di DrawLine ricevono un oggetto Pen, le coordinate del punto iniziale e le coordinate del punto finale. Le prime due variazioni ricevono le coordinate sotto forma di numeri a virgola mobile, mentre le ultime due variazioni ricevono le coordinate come valori integer. La prima e la terza variazione ricevono le coordinate sotto forma di elenco di quattro numeri distinti, mentre la seconda e la quarta variazione ricevono le coordinate come una coppia di oggetti Point (o PointF).

Eliminazione del concetto di posizione corrente

Si noti che nei metodi DrawLine riportati in precedenza sia il punto iniziale che il punto finale della linea vengono ricevuti come argomenti. Questo rappresenta una differenza evidente rispetto allo schema GDI, in cui si chiama MoveToEx(hdc, x1, y1, NULL) per impostare la posizione corrente della penna, seguito da LineTo(hdc, x2, y2) per tracciare una linea che inizia dal punto (x1, y1) e finisce al punto (x2, y2). In GDI+ il concetto di posizione corrente è stato abbandonato completamente.

Metodi distinti per tracciare e riempire

GDI+ consente una maggiore flessibilità nel tracciare i contorni e nel riempire l'interno di forme rispetto a GDI. In GDI è disponibile una funzione Rectangle, che consente di tracciare il contorno di un rettangolo e di riempirne l'interno in un unico passaggio. Il contorno viene tracciato con la penna correntemente selezionata e l'interno viene riempito con il pennello correntemente selezionato.

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);

In GDI+ sono disponibili metodi distinti per tracciare il contorno e riempire l'interno di un rettangolo. Nei parametri del metodo DrawRectangle della classe Graphics è incluso l'oggetto Pen e nei parametri del metodo FillRectangle è incluso l'oggetto Brush.

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);

Si noti che i metodi FillRectangle e DrawRectangle di GDI+ ricevono argomenti che specificano il margine sinistro, il margine superiore, la larghezza e l'altezza del rettangolo, a differenza della funzione Rectangle di GDI, a cui vengono associati argomenti che specificano il margine sinistro, destro, superiore e inferiore del rettangolo. Si noti inoltre che il metodo FromArg della classe Color di GDI+ dispone di quattro parametri. Gli ultimi tre paragrafi sono i consueti valori rosso, verde e blu. Il primo parametro è il valore alfa,che specifica il grado di fusione del colore selezionato con il colore di sfondo.

Creazione di regioni

In GDI sono disponibili svariate funzioni per la creazione di regioni: CreateRectRgn, CreateEllpticRgn, CreateRoundRectRgn, CreatePolygonRgn e CreatePolyPolygonRgn. Contrariamente alle aspettative, la classe Region di GDI+ non dispone di costruttori analoghi che accettino come argomenti rettangoli, ellissi, rettangoli arrotondati e poligoni. La classe Region di GDI+ fornisce un costruttore che riceve un oggetto Rectangle e un altro costruttore che riceve un oggetto GraphicsPath. Se si desidera creare una regione basata su un'ellisse, un rettangolo arrotondato o un poligono, è sufficiente creare un oggetto GraphicsPath (contenente ad esempio un'ellisse) e passare tale oggetto GraphicsPath a un costruttore Region.

In GDI+ la creazione di regioni complesse risulta facilitata grazie alla combinazione di forme e percorsi. Alla classe Region sono associati i metodi Union e Intersect, che possono essere utilizzati per arricchire una regione esistente con un percorso o un'altra regione. Lo schema GDI+ si rivela decisamente vantaggioso in quanto consente di non eliminare un oggetto GraphicsPath quando tale oggetto viene passato come argomento a un costruttore Region. In GDI è possibile convertire un percorso in una regione tramite la funzione PathToRegion, ma il percorso viene eliminato durante il processo. L'oggetto GraphicsPath non viene inoltre eliminato quando viene passato come argomento a un metodo Union o Intersect. È quindi possibile utilizzare un percorso dato come base per la creazione di svariate regioni distinte, come illustrato nell'esempio seguente. Si supponga che onePath sia un oggetto GraphicsPath (semplice o complesso) già inizializzato.

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);