Device Context

Alle Ausgaben werden durch das GDI (Graphics Device Interface) gesteuert, unabhängig vom gerade verwendeten Ausgabegerät wie z.B. Bildschirm oder Drucker. Damit das GDI geräteunabhängig arbeiten kann, greift es auf die Ausgabegeräte über entsprechende Gerätetreiber zu. Jeder Gerätetreiber für ein Ausgabegerät muss mindestens die folgenden zwei Aktionen durchführen können: einen Punkt setzen und eine Linie zeichnen. Alle anderen, komplizierteren Zeichenanweisungen die nicht direkt vom Ausgabegerät unterstützt werden, werden dann mit Hilfe des Gerätetreibers softwaremäßig emuliert.

Diese Kapitel stellt Ihnen eine Reihe von GDI-Funktionen vor, wobei viele innerhalb der MFC Klasse CDC gekapselt sind. Eine vollständige Aufzählung der GDI-Funktionen würden den Kursumfang sprengen, da WINDOWS, und damit auch die Klasse CDC, über 150 verschiedene GDI-Funktionen enthält.

Aber sehen wir uns zunächst die 'Schaltzentrale' für alle Ausgaben an, den DC (Device Context). Der DC ist eine Struktur die die Informationen enthält, wie eine Grafik- und Textausgaben zu erfolgen hat. So ist z.B. im DC die aktuelle Zeichenfarbe oder auch der gerade aktive Zeichensatz abgelegt. Bevor überhaupt Ausgaben in einem Fenster vorgenommen werden können, muss die Anwendung sich immer einen solchen DC holen. Alle Ausgabefunktionen benötigen diesen DC da er wie gesagt die aktuellen Einstellungen zum Zeichnen enthält. Da der DC eine Systemressource ist, muss er, wenn er nicht mehr benötigt wird, wieder freigegeben werden. Ein reservierter DC belegt relativ viel Systemspeicher. Eine Methode zum Holen eines DC kennen Sie schon, die API-Funktion BeginPaint(...). Sie liefert als Ergebnis ein Handle auf den DC (HDC) zurück. Diese Funktion wurde bereits im zweiten Kapitel Einfache WINDOWS Programme ohne MFC in der Lektion Darstellen des Fensterinhalts kurz besprochen. Das Gegenstück zu BeginPaint(...), die Funktion EndPaint(...), gibt den reservierten DC wieder frei.

Die MFC enthält als Wrapper-Klasse für den DC die Klasse CDC. Die Klasse CDC ist wiederum von der Klasse CObject abgeleitet und damit serialisierbar, d.h. Sie können die Einstellungen eines DC abspeichern und auch wieder laden. Auf eine vollständige Behandlung aller Methoden der CDC Klassen gehen wir in diesem Kapitel nicht ein da dies einfach zu viele sind. Hier hilft, wie so oft, nur ein Blick in die Online-Hilfe. Einfacher ist es da schon bei den public Eigenschaften der Klasse. Die CDC Klasse enthält nur die beiden public Eigenschaften m_hDC und m_hAttribDC. Die Eigenschaft m_hDC enthält den aktuellen DC und m_hAttribDC ist in der Regel eine Kopie von m_hDC die immer dann verwendet wird, wenn MFC-Methoden Informationen über den DC einholen.

Doch damit genug der Vorworte. Sehen wir uns jetzt an, wie man in einer MFC-Anwendung ein DC erhält. Hierbei müssen wir folgende Anwendungsarten unterscheiden:

  • Anwendungen die nicht auf dem Doc/View Modell basieren
  • und Anwendungen die das Doc/View Modell unterstützen.

Haben Sie eine Anwendung die nicht auf dem Doc/View Modell basiert, so erfolgt die Darstellung des Fensterhalts in der CWnd-Methode OnPaint(..). In den Beispielen vor der Einführung des Doc/Modells wurde in dieser Methode immer die Basisklassen-Methode mit aufgerufen damit der Fensterinhalt wieder als gültig gekennzeichnet wird. Dies ist ab sofort nicht mehr notwendig, da wir uns den DC nun selbst holen um das Fenster wieder als gültig zu kennzeichnen. Um einen DC zu erhalten stehen Ihnen zwei verschiedene Möglichkeiten zur Verfügung.

Die erste Möglichkeit einen DC zu erhalten ist der Aufruf der CWnd-Methode BeginPaint(...). Als Parameter erhält die Methode einen Zeiger auf eine Struktur vom Typ PAINTSTRUCT, die schon bei der Beschreibung der Funktion BeginPaint(...)  behandelt wurde. Wenn Sie hier klicken, können Sie sich nochmals die Struktur ansehen. In der Praxis werden Sie den Inhalt dieser Struktur jedoch nur selten selbst auswerten. Wurde die Methode erfolgreich ausgeführt, so liefert sie als Returnwert einen Zeiger auf ein CDC-Objekt und im Fehlerfall den Wert '0' zurück. Um das so erhaltene CDC-Objekt nach Beendigung der Zeichenoperationen wieder freizugeben ist die CWnd-Methode EndPaint(...) aufzurufen. Denken Sie immer daran das CDC-Objekt auch immer wieder freizugeben, da es den DC enthält der nicht unerhebliche Systemressourcen belegt.

Die zweite, und auch vorzuziehende Möglichkeit, in einem MFC-Programm einen DC zu erhalten, ist ein Objekt vom Typ CPaintDC in der OnPaint(...) Methode zu erstellen. Der Konstruktor von CPaintDC ruft intern die vorher erwähnte Methode BeginPaint(...) auf. Wird beim Beenden der Methode OnPaint(...) das CPaintDC Objekt zerstört, so wird automatisch dessen Destruktor aufgerufen. Und der Destruktor von CPaintDC ruft nun die Methode EndPaint(...) auf und gibt damit den DC wieder frei. Schlägt die Reservierung des DC fehl, so löst der Konstruktor von CPaintDC eine Exception (Ausnahme) von Typ CResourceException aus. Somit könnte eine OnPaint(...) Methode etwa wie folgt aussehen:

// OnPaint() zum Zeichnen
void CMyWnd::OnPaint()
{
    try
    {
        CPaintDC   CMyDC(this);       
// Zeiger auf akt. Fenster übergeben
                                      
// -> BeginPaint() wird aufgerufen
        CMyDC.MoveToEx(0,0);          
// ab hier kann jetzt gezeichnet werden
        ....
    }                                 
// CPaintDC wird zerstört
                                      
// -> EndPaint()wird aufgerufen
    catch(CResourceException)         
// Fehlerfall abfangen
    {
        ....
    }
}
Der CPaintDC wird am Ende des try-Blocks zerstört. Sie müssen die Zeichenanweisungen deshalb unbedingt innerhalb des try-Blocks durchführen!

Sehen wir uns noch eine etwas anders gelagerte Problematik an. In manchen Fällen (und einige werden Sie in diesem Kapitel noch kennen lernen) kann es notwendig sein, sich einen DC auch außerhalb der OnPaint(...) Methode zu holen. Dazu wird die CWnd-Methode GetDC(...) aufgerufen. Auch diese Methode liefert als Rückgabewert einen Zeiger auf ein CDC-Objekt, oder im Fehlerfall den Wert 0. Mit Hilfe dieses  CDC-Objekts kann nur  innerhalb der Client-Area des Fensters gezeichnet werden. Soll dagegen im gesamten Fenster gezeichnet werden, d.h. einschließlich der Titelzeile, des Rahmens usw., so kann der dazu notwendige DC mit der CWnd-Methode GetWindowDC(...) erzeugt werden. Beide CDC-Objekte müssen, wenn sie nicht mehr benötigt werden, unbedingt mit der CWnd-Methode ReleaseDC(...) freigegeben werden.

Obwohl mit Hilfe des so erhaltenen CDC-Objekts auch außerhalb des OnPaint(...)- bzw. des gleich eingeführten OnDraw(...)-Handlers der MFC gezeichnet werden kann, so beachten Sie dabei jedoch unbedingt, dass beim nächsten Aufruf des MFC-Handlers die soeben erstellte Ausgabe eventuell wieder zerstört wird. Sie müssen die neue Ausgabe im Regelfall ebenfalls in der OnPaint(...) bzw. OnDraw(...) durchführen. Beide Methode dienen eigentlich nur dazu, eine Ausgabe schnell und ohne Aufruf des MFC-Handlers darzustellen.

Kommen wir nun zu den Anwendungen die auf dem Doc/View Modell basieren. Hier ist die ganze Sache etwas einfacher. Anwendungen die auf dem Doc/View Modell basieren zeichnen nicht in der CWnd Methode OnPaint(...) sondern statt dessen in der CView Methode OnDraw(...). Und OnDraw(...) erhält als Parameter einen Zeiger auf ein bereits erstelltes CDC-Objekt übergeben, so dass Sie sich hier nicht mehr um die Erstellung bzw. Zerstörung des CDC-Objekts kümmern müssen. Die obige Methode würde damit in einer Doc/View Anwendung wie folgt aussehen:

// OnDraw() zum Zeichnen
void CMyView::OnDraw(CDC *pDC)
{
    pDC->MoveToEx(0,0);          
// es kann sofort gezeichnet werden!
    ....
}

Sehen wir uns noch zwei weitere nützliche Methoden der CDC-Klasse an. So kann es in manchen Fällen unter Umständen erforderlich sein, relative viele im DC abgelegte Zeichenattribute vorübergehend zu verändern. Anstelle nun alle Attribute einzeln auszulesen und abzuspeichern kann die CDC Methode SaveDC(...) aufgerufen werden. Sie speichert den aktuellen Zustand des DC auf einem internen Stack ab und liefert als Returnwert einen Index auf den abgespeicherten DC zurück. Schlägt die Funktion fehl, so liefert sie den Index 0 zurück Um später einen mittels der Methode SaveDC(...) abgespeichert DC-Zustand wieder herzustellen wird die CDC Methode RestoreDC(...) aufgerufen. Sie erhält als Parameter den von SaveDC(...) zurückgelieferten Index. Das nachfolgende Listing zeigt ein Beispiel für die Anwendung der beiden Methoden.

void CMyView::DrawSomething (CDC *pDC)
{
    int nSavedDC = pDC->SaveDC();   
// Hier aktuellen Zustand des DCs retten
    pDC->SetTextColor(...);         
// Einstellungen des DCs veraendern
    pDC->SetMappingMode(...);
    ....                            
// hier wird jetzt gezeichnet
    pDC->RestoreDC (nSavedDC);      
// Originalzustand wieder herstellen
}
Wurden mehrere DC mittels SaveDC(...) gerettet und wird dann mit RestoreDC(...) nicht der zuletzt gerettete DC-Zustand wieder hergestellt, so werden alle DC bis zum angegebenen Index automatisch gelöscht (automatic rollback).

Damit verlassen wir jetzt die Erstellung eines DC, nicht aber ohne den Hinweis, dass es eine ganze Reihe weiterer Methoden gibt die sich mit seiner Erstellung befassen. So kann z.B. mittels der CDC Methode Attach(...) ein WINDOWS-DC einem MFC CDC-Objekt zugewiesen werden. Mehr dazu entnehmen Sie bitte der Online-Hilfe zum Thema CDC.

Zum Schluss dieser Lektion werden wir uns noch ansehen, welche verschiedenen CDC Klassen die MFC zur Verfügung stellt und wofür Sie letztendlich eingesetzt werden. Als Basisklasse für alle DC dient die Klasse CDC. Sie enthält u.a. auch alle Zeichenmethoden. Von CDC abgeleitet sind vier weitere Klassen, deren Anwendungsgebiete in der nachfolgenden Tabelle aufgeführt sind.

CDC Klasse

Anwendungsgebiet

CClientDC Erstellt den DC mit Hilfe der Methode GetDC(...). Dieser DC wird immer dann eingesetzt, wenn außerhalb der OnPaint(...) bzw. OnDraw(...) Methode gezeichnet werden soll. Das Zeichnen ist hier nur innerhalb der Client-Area des Fensters möglich.
CMetaFileDC Erstellt einen DC, der die Ausgabe in ein WINDOWS Metafile umleitet (Datei-Extension WMF). Mehr dazu später in diesem Kapitel bei der Behandlung der WINDOWS Metafiles.
CPaintDC Erstellt den DC mit Hilfe der Methode BeginPaint(...). Er darf nur innerhalb der OnPaint(...) Methode eingesetzt werden. Die Methode OnDraw(...) erhält als Parameter einen Zeiger auf ein CDC-Objekt dieses Typs übergeben. Auch hier kann nur innerhalb der Client-Area des Fensters bzw. Ansichtsobjekts (Views) gezeichnet werden.
CWindowDC Wie CClientDC, nur dass das Zeichen jetzt im gesamten Fenster einschließlich des Rahmens und der Titelzeile möglich ist.

Und nochmals zur Wiederholung, weil's so wichtig für die Ausführung von Zeichenoperationen ist und man es leicht vergisst:

Zeichenoperationen werden nur innerhalb des Bereichs eines Fensters durchgeführt, der als ungültig markiert ist. Um einen Zeichenbereich als ungültig zu markieren stehen die beiden CWnd Methode Invalidate(...) und InvalidateRect(...) zur Verfügung. Mehrere Aufrufe dieser Methoden hintereinander erzeugen aber immer nur eine WM_PAINT Nachricht. Die WM_PAINT Nachricht selbst besitzt eine relativ niedrige Priorität, d.h. sie wird erst dann an die Anwendung weitergereicht, wenn keine andere Nachricht mehr in der Nachrichtenschlange der Anwendung steht. Soll der ungültige Inhalt eines Fensters sofort restauriert werden, unter Umgehung der Nachrichtenschlange, so kann dafür die CWnd Methode UpdateWindow(...) eingesetzt werden.

Damit beenden wir diese Lektion und werden uns in der nächsten Lektion den GDI-Objekten zuwenden.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470