A
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.
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.
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:
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
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) |
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
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.
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;
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;
}
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.
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 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
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.
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.
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.
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.