Previous Page TOC Index Next Page See Page

A

Windows-Programmierung: Ein Überblick und ein Blick in CWnd

Die Microsoft Foundation Classes wurden aus einem einzigen Grund geschrieben: die Windows-Programmierung zu vereinfachen, indem sie Klassen mit Methoden und Attributen zur Verfügung stellen, die Aufgaben bewältigen können, die bei allen Windows-Programmen gleich sind. Die MFC-Klassen stellen somit einen großen Nutzen für jeden Windows-Programmierer dar. Die einzelnen Methoden nehmen ihm eine große Anzahl an Arbeiten ab, die in jedem Windows-Programm wieder auftreten. Viele der Klassen haben eine enge Beziehung zu Strukturen und »Windows-Klassen« im grundlegenden Sinne des Wortes Klasse, unabhänngig von C++. Viele dieser Methoden sind direkt an die entsprechende API-Funktionen (Applikation Programming Interface) angelehnt, die erfahrenen Windows-Programmierern bereits bekannt sind. n

Programmieren für Windows

Wenn Sie bereits für Windows in C programmiert haben, wissen Sie, daß das Wort Klasse dazu benutzt wurde, die Definition eines Fensters zu beschreiben, lange bevor C++-Programmierung in die Windows-Programmierung Einzug hielt. Eine Fenster-Klasse ist die Grundlage eines jeden Windows-Programms. In einer Standardstruktur werden die Daten gespeichert, die diese Fenster-Klasse beschreiben, wobei schon vom Betriebssystem ein paar Standardfenster-Klassen bereitgestellt werden. Normalerweise erzeugt ein Programmierer eine neue Fenster-Klasse und registriert diese beim System mit der API-Funktion RegisterClass. Fenster, die auf dem Bildschirm erscheinen sollen und auf dieser Fenster-Klasse basieren, können dann mit der API-Funktion CreateWindow erzeugt werden.

Eine Fenster-Klasse im C-Stil

Die Struktur WNDCLASS, mit der die Fenster-Klasse beschrieben wird, entspricht der Struktur WNDCLASSA, die im folgenden Listing gezeigt wird:

Die Struktur WNDCLASSA aus WINUSER.H

typedef struct tagWNDCLASSA { 
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA

In WINUSER.H sind zwei sehr ähnliche Strukturen für Fenster-Klassen definiert, nämlich WNDCLASSA für Programme, die normale Zeichenketten verwenden, und WNDCLASSW für Unicode-Programme. Unicode-Programme werden in Kapitel 28 im Abschnitt über Unicode näher behandelt.


Den Quelltext zu WINUSER.H erhalten Sie zusammen mit dem Developer-Studio. Er befindet sich normalerweise im Verzeichnis \Programme\DevStudio\VC\include.

Wenn Sie ein Windows-Programm in C erstellen, müssen Sie diese Struktur mit Daten füllen. Die Elemente der Struktur WNDCLASS haben folgende Bedeutungen:

Fenstererzeugung

Wenn Sie noch nie zuvor ein Windows-Programm geschrieben haben, werden Sie vielleicht ein wenig von der Tatsache abgeschreckt, daß Sie die Struktur WNDCLASS selbst mit Werten füllen müssen. Dies ist jedoch der unbedingt notwendige erste Schritt zur Windows-Programmierung in C. Außerdem finden Sie sicherlich immer ein Beispielprogramm aus dem Sie Codeteile herauskopieren können, wie zum Beispiel aus diesem:

WNDCLASS wcInit; 

wcInit.sytle = 0;
wcInit.lpfnWndProc = (WNDPROC)MainWndProc;
wcInit.cbClsExtra = 0;
wcInit.cbWndExtra = 0;
wcInit.hInstance = hInstance;
wcInit.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ID_ICON));
wcInit.hCursor = LoadCursor(NULL, IDC_ARROW);
wcInit.hbrBackground = GetStockObject(WHITE_BRUSH);
wcInit.lpszMenuName = "DEMO";
wcInit.lpszClassName = "NewWClass";

return (RegisterClass(&wcInit));

Ungarische Notation


Vielleicht fragen Sie sich, was sich hinter einem Namen wie lpszClassName verbirgt, oder warum im vorangegangenen Beispiel der Name wcInit und nicht nur Init gewählt wurde. Die Programmierer bei Microsoft verwenden eine Namenskonvention, die als Ungarische Notation bezeichnet wird. Sie erhielt ihren Namen durch einen ungarischen Programmierer namens Charles Simonyi, der diese Namensgebung bei Microsoft einführte.

In der Ungarischen Notation erhalten die Variablen sprechende Namen, wie zum Beispiel Count oder ClassName. Wenn der Name aus mehreren Wörtern besteht, wird jedes Wort mit einem Großbuchstaben eingeleitet. Dann werden diesem sprechenden Namen noch Buchstaben vorangestellt, die den Typ der Variablen angeben, zum Beispiel nCount für eine Integer-Zahl und bFlag für eine Boolean-Variable (TRUE oder FALSE). Auf diese Weise kann ein Programmierer sofort den Typ einer Variable erkennen, um so nicht sinnvolle Operationen auf bestimmte Variablentypen vermeiden, wie zum Beispiel das Übergeben einer vorzeichenbehafteten Variable an eine Funktion, die Werte ohne Vorzeichen erwartet.

Diese Konvention hat sich inzwischen sehr weit verbreitet, obwohl es auch Programmierer gibt, die sie strikt ablehnen. Wenn Sie sich nach den »guten alten Zeiten« sehnen, in denen man sich darüber gestritten hat, wann man an welche Stelle im Code welche Sorte von Klammern zu setzen hat, oder besser noch, in welchem Zusammenhang man welche Klammer wie bezeichnet, aber niemanden mehr für diese Themen finden, dann können Sie diese Bestrebungen auf das Feld über den Sinn und Unsinn der Ungarischen Notation übertragen. Die Argumente für die Notation konzentrieren sich auf die Aussage »Man erwischt sich selber dabei, wie man dumme Fehler begeht«, währen sich die Argumente gegen die Notation unter »Es sieht schrecklich aus und ist schwer zu lesen« zusammenfassen lassen.

Praktisch gesehen ist es eine Tatsache, daß die Strukturen für API Funktionen und Klassen in den MFC alle die Ungarische Notation benutzen. Daher werden Sie sich schnell daran gewöhnen. Vielleicht übernehmen Sie diese Vorgehensweise auch für Ihre eigenen Variablendeklarationen. Die Präfixe sehen wie folgt aus:

Tabelle A.1: Übersicht über die Konventionen in der Ungarischen Notation (Forts.)

Präfix

Variablentyp

Kommentar

a

Array


b

Boolean


d

Double


h

Handle


i

Integer

»index to«

l

Long


lp

32-Bit-Zeiger (pointer)


lpfn

32-Bit-Zeiger auf eine Funktion


m_

Mitgliedsvariable


n

Integer

»number of«

p

Zeiger


s

String


sz

Nullterminierter String


u

vorzeichenloser Integer


C

Klasse (class)



Viele Leute fügen noch eigene Elemente in diese Konvention ein, wc in wcInit steht zum Beispiel für Window Class.

Das Füllen der Struktur wcInit und der Aufruf von RegisterClass ist die Standardvorgehensweise, wobei in diesem Beispiel eine Klasse mit dem Namen NewWClass registriert wird, die ein Menü mit dem Namen DEMO besitzt und eine Fensterprozedur mit dem Namen MainWndProc. Nach der Registrierung muß bei dieser altmodischen Vorgehensweise noch folgender Code geschrieben werden, um ein Fenster auf dem Bildschirm erscheinen zu lassen:

HWND hWnd; 
hInst = hInstance;
hWnd = CreateWindow(
"NewWClass",
"Demo 1",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);

if (!hWnd)
return(false);

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

Dieser Programmteil ruft zunächst CreateWindow, dann ShowWindow und UpdateWindow auf. Die Parameter für die API-Funktionen sind die folgenden:

CreateWindow liefert ein Fenster-Handle zurück û diese Fenster-Handles werden immer mit hWnd bezeichnet û und dieses Handle wird im Rest des Programms weiterverwendet. Wenn dieser Wert NULL ist, dann ist die Fenstererstellung fehlgeschlagen. Bei jedem Wert, der nicht NULL ist, war die Erzeugung erfolgreich, und das Handle wird an ShowWindow und UpdateWindows übergeben. Beide zusammen zeichnen das Fenster dann auf dem Bildschirm.

Handles


Ein Handle ist mehr, als nur ein Zeiger. Windows-Programme greifen auf Ressourcen, wie zum Beispiel Fenster, Symbole, Mauszeiger usw., mit Hilfe von Handles zu. Im Hintergrund gibt es dafür eine Handle-Tabelle, die die Adressen der Ressourcen gespeichert hat, und darüber hinaus noch Informationen über die Art der Ressource. Handles werden normalerweise an Funktionen übergeben, die den Zugriff auf Ressourcen benötigen, und sie werden von Funktionen zurückgeliefert, die Speicher für solche Ressourcen reservieren. Es gibt eine Anzahl von grundlegenden Typen von Handles: hWnd für ein Fensterhandle, HICON für ein Symbol-Handle usw. Egal, welche Art von Handle Sie benutzen, die Idee ist immer die gleiche. Mit Hilfe eines Handles können Sie eine Ressource erreichen, um sie für beliebige Aufgaben einzusetzen.

Kapselung der Windows-API

API-Funktionen erzeugen und manipulieren Fenster auf dem Bildschirm, ermöglichen das Zeichnen, verbinden Applikationen mit Hilfedateien, ermöglichen Multithreading (mehrere Programmfäden), verwalten den Speicher und vieles mehr. Durch die Kapselung dieser Funktionen in MFC-Klassen können Ihre Programme die gleichen Aufgaben durchführen, jedoch mit deutlich geringerem Programmieraufwand Ihrerseits.

Es gibt Tausende von API-Funktionen und es kann Monate dauern, bis man sich einigermaßen gut mit der API auskennt. Daher versucht dieses Buch nicht, eine Art Mini-Tutorial für die API anzubieten. Im Abschnitt »Programmieren für Windows«, früher in diesem Kapitel, wurden Sie an die beiden API-Funktionen RegisterClass und CreateWindow erinnert. Sie zeigen sehr gut die Schwierigkeiten bei der C-Programmierung mit dem API unter Windows und wo die Arbeit mit Hilfe der MFC einfacher wird. Eine Dokumentation über die API-Funktionen finden Sie innerhalb von Visual C++: Klicken Sie auf das Register InfoView und öffnen Sie den Zweig Plattform, SDK und DDK Dokumentation. Innerhalb dieses Themas öffnen Sie Plattform-SDK/Referenz/Funktionen und dann Win32-Funktionen. Dadurch erhalten Sie eine alphabetische Liste von Kategorien, wie zum Beispiel ArrangeIconicWindows bis CloseClipboard. Innerhalb dieser Kategorien sind die Funktionen ebenfalls alphabetisch angeordnet. Es gibt außerdem Indexeinträge, die zu bestimmten Funktionen führen.

CWnd im Detail

Die MFC-Klasse CWnd ist eine extrem wichtige Klasse. Ungefähr ein drittel aller MFC-Klassen benutzen sie als Basisklasse. Zu diesen Klassen gehören zum Beispiel CDialog, CEditView, CButton und viele mehr. Diese Klasse ist eine Art Hülle um die alte Fenster-Klasse und die dazugehörigen API-Funktionen zum Erstellen und Manipulieren von Fenster-Klassen. Eine öffentliche Elementvariable ist zum Beispiel m_hWnd. In ihr wird das Fenster-Handle gespeichert. Diese Variable wird durch die Elementfunktion CWnd::Create gesetzt und von fast allen anderen Elementfunktionen benutzt, wenn sie ihre zugehörigen API-Funktionen aufrufen.

Vielleicht kommen Sie jetzt auf die Idee, daß der Aufruf der API-Funktion CreateWindow automatisch vom CWnd-Konstruktor (CWnd::CWnd) erledigt wird, so daß beim Aufruf des Konstruktors zum Initialisieren eines Fensters gleichzeitig das entsprechende Fenster auf dem Bildschirm erzeugt wird. Das würde dem Programmierer eine Menge an Arbeit sparen, weil zumindest der Aufruf des Konstruktors nie vergessen werden kann. Trotzdem ist das nicht die Vorgehensweise, die Microsoft gewählt hat. Der Konstruktor sieht folgendermaßen aus:

CWnd::CWnd() 
{
AFX_ZERO_INIT_OBJECT(CCmdTarget);
}

AFX_ZERO_INIT_OBJECT ist ein Makro, das durch den Präprozessor des C++-Compiler eingesetzt wird. Es verwendet die C-Funktion memset, um jedes Byte jeder Elementvariablen in diesem Objekt auf Null zu setzen, wie hier gezeigt:

#define AFX_ZERO_INIT_OBJECT(base_class) \
memset(((base_class*)this)+1, 0, sizeof(*this) \
û sizeof(class base_class));

Microsoft hat aus einem bestimmten Grund den Aufruf von CreateWindow nicht in den Konstruktor übernommen. Ein Konstruktor kann keinen Funktionswert zurückliefern. Sollte bei der Erstellung des Fensters irgend etwas schiefgehen, so gibt es keinen eleganten Weg damit umzugehen. Daher tut der Konstruktor fast gar nichts, so daß auch nichts fehlschlagen kann. Der Aufruf von CreateWindow erfolgt dann in der Elementfunktion CWnd::Create, oder in der stark verwandten Funktion CWnd::CreateEx, die so aussieht wie im folgenden Listing:

Listing A.1: Listing CWnd::CreateEx() aus WINCORE.CPP

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, 
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;

Listing A.1: Listing CWnd::CreateEx() aus WINCORE.CPP (Forts.)


cs.hInstance = AfxGetInstanceHandle(); 
cs.lpCreateParams = lpParam;

if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG
if (hWnd == NULL)
{
TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X\n",
GetLastError());
}
#endif

if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if CreateWindowEx fails too soon

if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}


Den Quelltext zu WINCORE.CPP erhalten Sie zusammen mit dem Developer-Studio. Er befindet sich normalerweise im Verzeichnis \Programme\DevStudio\VC\mfc\src.

Hierbei wird eine Struktur CREATESTRUCT erstellt, die der Struktur WNDCLASS sehr ähnlich ist. Sie wird mit dem Werten gefüllt, die an CreateEx übergeben werden. Außerdem werden die Funktionen PreCreateWindow, AfxHookWindowCreate, ::CreateWindow und AfxUnhookWindowCreate verwendet, bevor dann schließlich hWnd überprüft wird und die Funktion zurückkehrt.


Das Präfix AFX bei vielen nützlichen MFC-Funktionen stammt noch aus der Zeit, als der interne Name für die Klassenbibliothek »Application Framework« war. Die beiden Doppelpunkte (::) im Aufruf von CreateWindow identifizieren sie als API-Funktion. Die anderen Funktionen sind einfache Elementfunktionen von CWnd die andere Funktionalitäten initialisieren.

Auf den ersten Blick sieht der Code nicht nach einer großen Einsparung aus: Sie müssen eine Instanz erzeugen, dann die Create Funktion aufrufen und dabei genauso viele Parameter übergeben wie in der herkömmlichen Vorgehensweise in C. Wo liegt also der Vorteil? Nun CWnd ist eigentlich eine Klasse, von der hauptsächlich andere Klassen abgeleitet werden. In diesen abgeleiteten Klassen wird die Vorgehensweise schon sehr viel einfacher. Nehmen Sie als Beispiel die Klasse CButton. Das Verhalten dieser Klasse ist sehr eingeschränkt. Der Benutzer kann zum Beispiel die Größe einer Schaltfläche nicht verändern. Die Elementfunktion Create sieht so aus:

BOOL CButton::Create(LPCTSTR lpszCaption, DWORD dwStyle, 
const RECT& rect, CWnd* pParentWnd, UINT nID)
{
CWnd* pWnd = this;
return pWnd->Create(_T("BUTTON"), lpszCaption, dwStyle, rect, pParentWnd, nID);
}

Das sind schon eine ganze Reihe weniger Parameter. Wenn Sie eine Schaltfläche haben möchten, erzeugen Sie eine. Die Klassenhierarchie füllt den Rest automatisch.

Die MFC-Klassen in den Griff bekommen

Es gibt insgesamt über 200 MFC-Klassen. Warum sind es so viele, und was tun sie im einzelnen? Noch viel wichtiger ist die Frage, wie man sich da einen Überblick verschaffen kann, um zu wissen, wann man welche Klasse einsetzt. Solche Fragen lassen sich vollständig nur auf sehr vielen Buchseiten beantworten. Die erste Hälfte dieses Buches hat sich nur damit beschäftigt, die am häufigsten benutzten Klassen vorzustellen. Dieser Abschnitt beschäftigt sich nun mit einigen der wichtigsten Basisklassen.

CObject

Die Abbildung A.1 zeigt einen Überblick über den Ableitungsbaum der MFC-Klassen auf einer sehr hohen Ebene. Es gibt nur sehr wenige MFC Klassen, die nicht von CObject abgeleitet sind. CObject beinhaltet die gesamte Grundfunktionalität, die die meisten MFC-Klassen (und wahrscheinlich die meisten der von Ihnen erstellten Klassen) benötigen. Dazu gehört die Unterstützung von Persistenz oder Ausgaben zu Diagnosezwecken. Außerdem können Klassen, die von CObject abgeleitet wurden in MFC-Container-Klassen aufbewahrt werden. Mehr darüber finden Sie im Anhang E.

Abbildung A.1: Fast alle MFC-Klassen werden von CObject abgeleitet

siehe Abbildung

CCmdTarget

Einige der Klassen, die von CObject abgeleitet werden, wie zum Beispiel CFile und CException und die von ihnen abgeleiteten Klassen, benötigen keine direkte Interaktion mit dem Benutzer oder dem Betriebssystem mit Hilfe von Nachrichten. Alle Klassen die jedoch über diese Fähigkeiten verfügen müssen, sind von CCmdTarget abgeleitet. Die Abbildung A.2 zeigt einen Überblick über die von CCmdTraget abgeleiteten Klassen.

Abbildung A.2: Jede Klasse, die Kommandos erhält, muß von CCmdTarget abgeleitet sein.

siehe Abbildung

CWnd

Wie bereits erwähnt, ist CWnd eine extrem wichtige Klasse. Nur Klassen, die von CWnd abgeleitet wurden können Nachrichten empfangen. Programmfäden und Dokumente können Kommandos empfangen, aber keine Nachrichten.


Kapitel 4 untersucht die Unterschiede zwischen Kommandos und Nachrichten genauer, während in Kapitel 5 Dokumente näher erläutert werden. Im Kapitel 27 werden Programmfäden genauer unter die Lupe genommen.

CWnd liefert fensterorientierte Funktionalität, wie zum Beispiel Aufrufe von CreateWindow und DestroyWindow, Funktionen zum Abarbeiten von Nachrichten, Kommunikation mit der Zwischenablage und vieles mehr. Insgesamt sind es nahezu 250 Funktionen. Nur wenige davon müssen in abgeleiteten Klassen überschrieben werden. Die Abbildung A.3 zeigt Klassen, die von CWnd abgeleitet werden. Um durch die vielen Steuerelementklassen nicht die Übersicht zu verlieren, sind diese unter einem Punkt »Steuerelementklassen« zusammengefaßt.

Abbildung A.3: Jede Klasse, die eine Nachricht erhält, muß von der Klasse CWnd abgeleitet werden, welche eine große Anzahl fensterbezogener Funktionen bietet.

siehe Abbildung

Alle anderen Klassen

In diesen Abbildungen haben Sie bisher zehn Klassen gesehen. Wo sind die anderen über 200 Klassen? Viele davon finden Sie innerhalb eines bestimmten Zusammenhangs innerhalb des ganzen Buchs vorgestellt. Wenn Sie nach einer speziellen Klasse suchen, können Sie auch einen Blick in den kompletten Quelltext der MFC-Klassen werfen, der mit Visual C++ geliefert wird. Das Lesen des Quelltextes ist allerdings ein schwieriger Weg, herauszufinden, wie eine Klasse arbeitet. Manchmal ist so eine Detailfülle aber auch sehr hilfreich.


© 1997 Que
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH
Elektronische Fassung des Titels: Special Edition Visual C++ 5.0, ISBN: 3-8272-1019-4

Previous Page Page Top TOC Index Next Page See Page