erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC ( Stand: 31.05.2002 )
Zurueck zum Inhaltsverzeichnis
Kapitel 5 - Windows mit MFC ohne Assistent
5.1 Ein einfaches Window ohne Assistent erstellen
Wie Sie bereits wissen, bilden Fenster und Nachrichten die Grundlage von MS Windows. Nachdem Sie jetzt mit Hilfe des Anwendungsassistenten bereits mehrere Dialoganwendungen erstellt haben, werden wir nun ein einfaches Fenster ohne die Unterstützung des Assistenten erstellen. Sie sehen hierbei, daß es mittels MFC relativ einfach ist, ein Fenster zu erzeugen. Die Basis hierfür bildet das Zusammenspiel von Anwendungs- und Fenster-Klassen der MFC.
Aufgrund der Organisation von Visual C++ mit Hilfe von Arbeitsbereichen und Projekten sind vorab einige Vorbereitungen zu treffen, damit die von uns erstellten Programmdateien das Zusammenspiel von Compiler und Linker problemlos passieren:
Abb. 5.1: Die MFC werden in das Projekt eingebunden - hier für die Debug-Version
Wir könnten zur Vereinfachung der Dateistruktur den Programmcode der Header-Datei anstelle von #include "EinfachesFenster.h" (siehe unten) direkt in die Quellcode-Datei integrieren. Dies ist jedoch nicht die übliche Praxis. Im Rahmen der Objektorientierung und modularen Programmierung werden Klassendeklarationen zur leichten Wiederverwendbarkeit in anderen Projekten in sogenannten Header-Dateien abgelegt. Das Einbinden in einer Quellcode-Datei erfolgt anschließend über die Präprozessor-Anweisung #include.
Wenn Sie sich das Arbeitsverzeichnis unseres Projektes im Explorer anschauen, finden Sie dort bisher folgende Dateien:
EinfachesFenster.dsp (Projektdatei)
EinfachesFenster.dsw (Arbeitsbereichsdatei)
EinfachesFenster.h (Header-Datei)
EinfachesFenster.cpp (Quellcode-Datei)
EinfachesFenster.ncb
(EinfachesFenster.opt)
Die einfache Gesamtstruktur erkennt man am besten in der Dateiansicht:
Abb. 5.2: Dateiansicht mit
Header-
und Quellcode-Datei
Für die Erstellung eines einfachen MFC-Anwendungs-Skelettes werden wir folgende Schritte unternehmen:
Wir erzeugen eine Fenster- und Anwendungsklasse durch Ableitung eigener Klassen aus den MFC-Klassen CFrameWnd und CWinApp:
Schritt 1: Einbinden von <afxwin.h>
Schritt 2: Abgeleitete Klasse von CWinApp
erzeugen
Schritt 3: Abgeleitete Klasse von CFrameWnd
erzeugen
Wir geben hierzu folgenden Source-Code in die
Header-Datei
ein:
#include
<afxwin.h> //Schritt
1
class
CMyApplication
: public CWinApp //Schritt
2
class
CMyWindow
: public CFrameWnd //Schritt
3
|
Die in der Header-Datei enthaltenen Klassen CMyApplication und CMyWindow sind "Baupläne" für unser Applikations- und Fensterobjekt. Beide Objekte werden wir nun in der Quellcode-Datei erstellen:
Schritt 4: Die Header-Datei einbinden
Schritt 5: Ein Objekt der abgeleiteten
Anwendungsklasse
erzeugen
Schritt 6: Ein Objekt der abgeleiteten Fensterklasse
im Konstruktor CMyWindow::CMyWindow()
erzeugen
Schritt 7: CWinApp::InitInstance()
überschreiben
Geben Sie in die Quellcode-Datei folgendes ein:
#include
"EinfachesFenster.h" //Schritt
4
CMyApplication MyApp; //Schritt 5 CMyWindow::CMyWindow()
//Schritt
6
BOOL
CMyApplication::InitInstance()
//Schritt
7
|
Nach dem Starten sollte folgendes Standard-Fenster erscheinen:
Abb. 5.3: Ein Standard-Fenster zeigt sich
Wir werden nun die einzelnen Schritte näher
beleuchten. Beginnen wir mit dem Einbinden von afxwin.h in die
Header-Datei.
Am besten versteht man die Wirkung von Anweisungen,
wenn man Sie versuchsweise einfach weg läßt.
Setzen Sie bitte das C++-Kommentar-Zeichen "//"
vor
#include
<afxwin.h> und starten Sie erneut.
Die wesentlichen Fehlermeldungen des Compilers sind:
'CWinApp' : Basisklasse undefiniert
'CFrameWnd' : Basisklasse undefiniert
'Create' : nichtdeklarierter Bezeichner
'NULL' : nichtdeklarierter Bezeichner
'_T' : nichtdeklarierter Bezeichner
Damit ist die Sache klar. In afxwin.h befinden sich die MFC und wesentliche Bezeichner.
Sie werden nun sehen, wie Sie leicht weiter in die Tiefe der MFC gehen können. Klicken Sie in der Header-Datei mit der rechten Maustaste auf das Wort CWinApp und wählen Sie dann "Gehe zu Definition von CWinApp". Folgende Meldung erscheint:
Abb. 5.4: Die Browse-Informationen sind noch nicht verfügbar
Antworten Sie mit Ja,
damit die Browse-Informationen erstellt werden. Dies öffnet auf
einfache
Weise auch das Tor zur MFC.
Sie landen zunächst bei der Ableitung der
MFC-Klasse
CWinApp von CWinThread:
class CWinApp : public
CWinThread
{ ...
Klicken Sie nun mit der rechten Maustaste auf
CWinThread und wählen "Gehe zu Definition von CWinThread"
etc.
Dann klettern Sie die MFC-Klassenhierarchie
hinauf.
class CWinThread : public
CCmdTarget
class CCmdTarget : public CObject
Auf diese Weise können Sie bequem im
komplexen
Innenleben der MFC schnuppern.
In der Definition der Klasse CWinApp
findet
man z.B. folgende Zeilen:
class CWinApp : public
CWinThread
{
...
int m_nCmdShow;
...
void SetDialogBkColor( COLORREF
clrCtlBk = RGB(192, 192, 192),
COLORREF clrCtlText = RGB( 0, 0, 0) );
...
BOOL Enable3dControls(); //
use CTL3D32.DLL for 3D controls in dialogs
...
virtual BOOL InitInstance();
...
};
Sie erkennen hier, daß die in
ShowWindow(...)
verwendete Variable m_nCmdShow eine Member-Variable der MFC-Klasse
CWinApp
ist.
Sie finden hier auch die virtuelle
Member-Funktion
InitInstance().
Da wir uns bereits mit Dialogfeldern beschäftigt haben, ist es für Sie vielleicht interessant zu sehen, daß in der Deklaration der Member-Funktion SetDialogBkColor( COLORREF clrCtlBk, COLORREF clrCtlText) auch die Standardfarben RGB( 192, 192, 192 ) für den Hintergrund und RGB( 0, 0, 0 ) für Texte in Dialogen vorgegeben werden. Hier findet sich auch Enable3dControls() sowie ein Hinweis auf die Datei CTL3D32.DLL, die für die 3D-Darstellung in Dialogfenstern sorgt.
Falls Sie die in traditionellen
Windows-Programmen
verwendete Header-Datei namens windows.h vermissen: afxwin.h
integriert über die include-Kette afx.h ... afxver_.h ...
afxv_w32.h
bereits das klassische windows.h. Spüren Sie dieser Kette mit der
Suchfunktion (Strg + F) nach. Prüfen Sie auch selbst, daß
m_pMainWnd
(vom Typ CWnd*) keine Member-Variable der Klasse CWinApp, sondern deren
Elternklasse CWinThread ist.
Nun wechseln wir zur Quellcode-Datei. Die entscheidende Verknüpfung zwischen Fenster und Anwendung findet man in folgenden Zeilen:
m_pMainWnd = new CMyWindow;
m_pMainWnd ->ShowWindow(
m_nCmdShow
);
Der Member-Variable m_pMainWnd der Klasse
CWinThread
wird hier die Adresse des neu erstellten Fensters übergeben.
Für die Anzeige auf dem Bildschirm sorgt die
Funktion ShowWindow(...), eine Member-Funktion der Klasse CFrameWnd:
BOOL ShowWindow( int nCmdShow )
Die abschließende Anweisung "return TRUE;" ist notwendig, um unsere Anwendung vollständig aufzurufen. Wenn Sie den Rückgabewert von InitInstance() auf FALSE setzen, wird das Fenster zwar erzeugt und kurz angezeigt, aber sofort wieder abgebrochen.
Unsere Anwendung wird in dem Moment realisiert, in dem ein Objekt der von CWinApp abgeleiteten Klasse erzeugt wird:
CMyApplication MyApp;
Damit sind die wesentlichen Zusammenhänge zwischen Anwendung und Fenster dargestellt. Die Festlegung der Erscheinungsform des Fensters wird beim Erzeugen durch die Member-Funktion CFrameWnd::Create erledigt:
Create ( NULL, _T( "MFC-Anwendungsskelett") );
Wenn Sie, wie oben beschrieben, in die Klasse CFrameWnd einsteigen, finden Sie folgende Deklaration dieser Member-Funktion:
BOOL Create
(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle
= WS_OVERLAPPEDWINDOW,
const RECT& rect
= rectDefault,
CWnd* pParentWnd
= NULL, // != NULL for popups
LPCTSTR lpszMenuName
= NULL,
DWORD dwExStyle
= 0,
CCreateContext* pContext
= NULL
);
Wie Sie sehen, nimmt MFC uns mit der von CWnd
abgeleiteten Klasse CFrameWnd eine Menge Festlegungen ab.
Notwendig
ist nur der Klassenname (NULL für Standard) und der Fenstertitel.
Alles andere ist in der Deklaration bereits vorbelegt und kann bei
Bedarf
natürlich überschrieben werden.
5.2 Nachrichtenverarbeitung
Das im vorigen Kapitel erstellte Fenster ist für Anwendungen noch nicht tauglich, da ihm die Fähigkeit fehlt, Nachrichten zu verarbeiten. Diesen wichtigen Mechanismus werden wir nun in unsere Fensterklasse integrieren:
In der Header-Datei ergänzen wir hierzu das Makro DECLARE_MESSAGE_MAP():
class
CMyWindow
: public CFrameWnd
{
public:
CMyWindow();
DECLARE_MESSAGE_MAP()
};
In der Quellcode-Datei ergänzen wir zwei weitere Makros:
CMyApplication MyApp;
CMyWindow::CMyWindow()
{
...
}
BOOL
CMyApplication
:: InitInstance()
{
...
}
BEGIN_MESSAGE_MAP
( CMyWindow, CFrameWnd )
END_MESSAGE_MAP()
Sie deklarieren also im Header die sogenannte Message-Map (Nachrichtentabelle), die im Quellcode zwischen BEGIN_MESSAGE_MAP und END_MESSAGE_MAP eingeschlossen wird. BEGIN_MESSAGE_MAP muß sowohl die eigene als auch die Elternklasse als Parameter aufnehmen. Das liegt daran, daß die Message-Map an abgeleitete Klassen vererbt wird. Daher wird bei erfolgloser Suche in der eigenen Klasse die Suche in der Elternklasse und evtl. in deren Elternklasse weiter geführt.
Nun werden wir diese Message-Map mit Inhalt
füllen.
Unsere Anwendung soll auf einen einfachen linken und rechten Mausklick
reagieren.
Diese Arbeit nimmt uns normalerweise der
Klassenassistent
ab. Innerhalb der Message-Map werden sogenannte Message Macros
eingebunden.
Diese haben (mit Ausnahme von WM_COMMAND) den gleichen Namen wie die
Standard-Windows-Nachrichten,
verwenden jedoch ON_ als Präfix. Die Nachricht für das
Drücken
der linken Maustaste ist WM_LBUTTONDOWN(), das entsprechende Makro hat
also den Namen ON_WM_LBUTTONDOWN(). Wir binden nun
die Message-Makros für linke und rechte Maustaste in unser
Programm
ein:
CMyApplication MyApp;
CMyWindow::CMyWindow()
{
...
}
BOOL
CMyApplication
:: InitInstance()
{
...
}
BEGIN_MESSAGE_MAP
(CMyWindow, CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()
Nun müssen wir sogenannte Message-Handler (Nachrichtenbehandlungsroutinen) deklarieren und einfügen. Zur korrekten Erstellung der Parameter benötigt man die Dokumentation der jeweiligen Funktion in MSDN. Die Deklaration erfolgt wie gewohnt in unserer Header-Datei:
class
CMyWindow
: public CFrameWnd
{
public:
CMyWindow();
afx_msg void
OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void
OnRButtonDown(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
In der Quellcode-Datei können wir diese Funktionen implementieren. Zur besseren Übersicht benutzen wir die bereits bekannte Funktion CWnd::MessageBox(...).
#include "EinfachesFenster.h"
CMyApplication MyApp;
CMyWindow::CMyWindow()
{
Create
( NULL, _T("MFC-Anwendungsskelett") );
}
BOOL
CMyApplication
:: InitInstance()
{
m_pMainWnd
= new CMyWindow;
m_pMainWnd
->ShowWindow( m_nCmdShow );
return
TRUE;
}
BEGIN_MESSAGE_MAP
(CMyWindow, CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()
void
CMyWindow::OnLButtonDown(UINT
nFlags, CPoint point)
{
MessageBox(
"Linke Maus", "Info" );
}
void
CMyWindow::OnRButtonDown(UINT
nFlags, CPoint point)
{
MessageBox(
"Rechte Maus", "Info" );
}
Sie fragen sich jetzt vielleicht, woher MFC eigentlich weiß, welche Funktion auf welche Nachricht ausgeführt werden soll. Klicken Sie zur Beantwortung dieser Frage mit der rechten Maus in der Quellcode-Datei auf ON_WM_LBUTTONDOWN(). Im Kontextmenü wählen Sie "Gehe zu Definition von ON_WM_LBUTTONDOWN()". Sie sind damit wieder in das interessante Innenleben der MFC-Bibliothek eingedrungen und finden dort den gesuchten Zusammenhang zwischen Message-Makro, Message und Message-Handler:
#define ON_WM_LBUTTONDOWN()
\
{ WM_LBUTTONDOWN, 0, 0, 0,
AfxSig_vwp, \ (AFX_PMSG) (AFX_PMSGW)
( void ( AFX_MSG_CALL CWnd::* ) (
UINT, CPoint ) ) &OnLButtonDown },
Message-Macro: | ON_WM_LBUTTONDOWN() |
Message: | WM_LBUTTONDOWN |
Message-Handler: | OnLButtonDown(...) |
Nach dem Kompilieren meldet sich nach einem
Mausklick
die jeweilige Message-Box. Damit haben wir unserem Fenster ein sehr
wichtiges
Element von MS Windows, nämlich die Nachrichtenverarbeitung,
eingehaucht. Für diese Kleinarbeit ist normalerweise der
Klassenassistent
zuständig. Dieser nimmt uns z.B. die Wahl der richtigen Parameter
ab.
5.3 WM_PAINT und OnPaint einbinden
In den vorangehenden Kapiteln erzeugten wir ein
Standard-Fenster
auf Basis der MFC-Klasse CFrameWnd.
Das Hinzufügen der MFC-Makros
DECLARE_MESSAGE_MAP(),
BEGIN_MESSAGE_MAP (CMyWindow, CFrameWnd)
und
END_MESSAGE_MAP()
erlaubte den Aufbau einer Nachrichtenbehandlung
für unser Fenster.
Ein weiterer Grundpfeiler der Windows-Programmierung ist die Darstellung von Fensterinhalten auf Basis der Nachricht WM_PAINT. Diese Nachricht wird von MS Windows immer dann ausgelöst, wenn ein Neuzeichnen des Fensters notwendig ist. Wir werden nun diesen Mechanismus in unser Beispiel einfügen und austesten. Was benötigen wir? Unsere Anwendung soll auf WM_PAINT reagieren und einen entsprechenden Message-Handler anstoßen. Das kennen Sie bereits aus dem letzten Kapitel. Fügen Sie daher bitte folgende Ergänzungen ein:
EinfachesFenster.h:
#include
<afxwin.h>
class
CMyApplication
: public CWinApp
{
public:
virtual
BOOL InitInstance();
};
class
CMyWindow
: public CFrameWnd
{
public:
CMyWindow();
afx_msg
void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg
void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg
void OnPaint();
DECLARE_MESSAGE_MAP()
};
EinfachesFenster.cpp:
#include
"EinfachesFenster.h"
CMyApplication
MyApp;
CMyWindow::CMyWindow()
{
Create(
NULL, _T("MFC-Anwendungsskelett") );
}
BOOL
CMyApplication
:: InitInstance()
{
m_pMainWnd
= new CMyWindow;
m_pMainWnd
->ShowWindow( m_nCmdShow);
return
TRUE;
}
BEGIN_MESSAGE_MAP
(CMyWindow, CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
void
CMyWindow::OnLButtonDown(UINT
nFlags, CPoint point)
{
MessageBox("Linke
Maustaste","Info");
}
void
CMyWindow::OnRButtonDown(UINT
nFlags, CPoint point)
{
MessageBox("Rechte
Maustaste","Info");
}
void
CMyWindow::OnPaint()
{
CPaintDC
dc(this);
dc.TextOut(
10, 10, _T("HALLO WELT !!!") );
}
Unser Fenster gibt nun den Text "HALLO WELT !!!" an Pixel-Position (10, 10) aus. Sie können das Fenster nun in der Größe verändern, aus dem Bild und wieder zurück verschieben oder überdecken und wieder freigeben, immer wird die Textausgabe sofort aktualisiert. Dies ist ein wichtiger Mechanismus, um stabile Fensterinhalte zu erhalten. Die Schnittstelle zwischen Programm und Hardware ist CPaintDC, ein Gerätekontext (device context), der unabhängig von Grafikkarte und Monitor für die standardisierte Bildschirmausgabe sorgt. Die Funktion TextOut(...) funktioniert bei konstanten Strings einfach durch Angabe der x- und y-Position und des Strings.
Wenn Sie einen String nicht an einem auf die linke obere Ecke bezogenen Punkt ausgeben wollen, sondern z.B. im Fenster flexibel zentrieren möchten, ist eine andere Textausgabe-Funktion besser geeignet, nämlich CDC::DrawText(...). Zusätzlich fügen wir noch CDC::SetTextColor(...) und CDC::SetBkColor(...) ein, um zu zeigen, wie man Vorder- und Hintergrundfarbe der Textausgabe festlegen kann:
void
CMyWindow::OnPaint()
{
CPaintDC
dc(this);
CRect
rect;
GetClientRect(&rect);
dc.SetTextColor
( RGB( 255, 0, 0 ) );
dc.SetBkColor
( RGB( 0, 255, 0 ) );
dc.DrawText(
_T( "Die MFC (Macht für C++) ist mit Dir !!!" ),
&rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER );
}
Nachfolgend die genaue Syntax der zur
Zeichenausgabe
eingesetzten Member-Funktionen im Überblick
(Detaillierte Ausführungen zu diesen Funktionen
finden Sie z.B. in MSDN):
BOOL CDC::TextOut( int x,
int y, const CString& str );
int CDC::DrawText( const
CString&
str, LPRECT lpRect, UINT nFormat );
void CWnd::GetClientRect(
LPRECT
lpRect ) const;
virtual COLORREF CDC::SetTextColor(
COLORREF crColor );
virtual COLORREF CDC::SetBkColor(
COLORREF crColor );
5.4 Mit einer eigenen WNDCLASS ein Icon anzeigen
Sie haben sich sicher schon gewundert, daß bei Einsatz der Standard-WNDCLASS durch Create( NULL, ... ) nur ein Standard-Icon vorhanden ist. Dieses Icon dient der visuellen Identifizierung einer Anwendung und wird in der Taskleiste bzw. links im Fenstertitel angezeigt. Da Sie sicher den Luxus eines individuellen Icons anstreben, müssen wir den ersten NULL-Zeiger in Create gegen eine spezielle WNDCLASS-Struktur austauschen. Diese grundlegende Struktur ist wie ein Bauplan für Fenster. Sie können dort z.B. Hintergrundfarbe, Icon und Cursor vorgeben.
Derartige WNDCLASS-Strukturen werden in Verbindung mit der modernen MFC-Funktion AfxRegisterWndClass(...) oder der klassischen API-Funktion RegisterClass(...) verwendet. Es handelt sich hierbei jedoch nicht um wirkliche "Klassen" im Sinne der Objektorientierung, sondern lediglich um Strukturen. Verwechseln Sie diese Art von "Fensterklassen" daher nicht mit den echten MFC-Fensterklassen auf Basis CWnd.
In der Quellcode-Datei ergänzen Sie nun bitte wie folgt:
#include "EinfachesFenster.h"
CMyApplication MyApp;
CMyWindow::CMyWindow()
{
CString
strWndClass =
AfxRegisterWndClass
(
CS_HREDRAW | CS_VREDRAW, MyApp.LoadStandardCursor( IDC_ARROW ),
( HBRUSH ) ( COLOR_WINDOWTEXT + 1 ),
MyApp.LoadStandardIcon( IDI_WINLOGO )
);
Create(
strWndClass, _T("MFC-Anwendungsskelett") );
}
BOOL
CMyApplication::InitInstance()
...
Im Konstruktor der Klasse CMyWindow setzen wir AfxRegisterWndClass(...) ein. Solche Funktionen mit dem Präfix Afx (Application File Extension) gehören zu keiner MFC-Klasse und sind daher global wirksam. Hier handelt es sich um eine Erweiterung der API.
In MSDN finden Sie zu dieser Funktion ausführliche Angaben für eigene Experimente. Die genaue Syntax dieser Funktion lautet:
LPCTSTR AFXAPI AfxRegisterWndClass
(
UINT
nClassStyle,
HCURSOR
hCursor
= 0,
HBRUSH
hbrBackground
=
0,
HICON
hIcon
= 0
);
Der Rückgabewert dieser Funktion wird anschließend in Create(...) verwendet, um ein Fenster zu erzeugen. Es handelt sich hierbei um einen NULL-terminierten String, der den Klassennamen enthält. Der Name wird durch die Microsoft Foundation Class Library erzeugt. Der Rückgabewert ist ein Zeiger auf einen statischen Puffer. Um diesen zu speichern, weist man diesen Zeiger einer Variable vom Typ CString zu, wie wir es in unserer Quellcode-Datei realisiert haben.
Nun zu den Parametern dieser Funktion:
Der erste Parameter UINT nClassStyle ist
wichtig. Setzen Sie diesen einmal auf den Wert 0. Sie beobachten dann
merkwürdige
Ereignisse, wenn Sie das Fenster in seiner Breite oder Höhe
verändern.
Es erfolgt in diesem Fall nämlich kein komplettes Neuzeichnen des
Fensters, und dann funktioniert unsere zentrierte Textausgabe nicht
mehr.
Mit der Kombination CS_HREDRAW | CS_VREDRAW ist dieses Thema
sofort
erledigt:
CS_HREDRAW:
Zeichnet das gesamte Fenster neu, wenn eine Bewegung
oder Größenänderung die Breite der "client
area"
verändert.
CS_VREDRAW:
Zeichnet das gesamte Fenster neu, wenn eine Bewegung
oder Größenänderung die Höhe der "client
area"
verändert.
Wenn Sie möchten, daß man bei Ihren Anwendungen verzweifelt den Ausschalter sucht, dann fügen Sie CS_NOCLOSE hinzu. Probieren Sie es aus. Zum Glück gibt es noch den Taskmanager (Strg + Alt + Entf) zum gewaltsamen Schließen von derartigen Anwendungen.
Wer Doppelklicks mit der Maus zulassen und verarbeiten möchte, sollte CS_DBLCLKS einbinden.
Der zweite Parameter legt den Cursor fest. Mit der Anweisung
MyApp.LoadStandardCursor( IDC_ARROW )
legt man den Standard-Pfeil als Cursor fest. Wenn Sie hier NULL eingeben, zeigt sich zunächst der Busy-Cursor, und erst beim Drücken der Maustaste wechselt der Cursor zum Pfeil. Das ist nicht gerade das, was ein Anwender erwartet.
Die Hintergrundfarbe des Fensters wird im dritten Parameter (vom Typ HBRUSH oder CBrush) bestimmt. Man kann hier z.B. eine der Windows-Standard-Farben verwenden:
COLOR_ACTIVEBORDER, COLOR_ACTIVECAPTION, COLOR_APPWORKSPACE, COLOR_BACKGROUND, COLOR_BTNFACE, COLOR_BTNSHADOW, COLOR_BTNTEXT, COLOR_CAPTIONTEXT, COLOR_GRAYTEXT, COLOR_HIGHLIGHT, COLOR_HIGHLIGHTTEXT, COLOR_INACTIVEBORDER, COLOR_INACTIVECAPTION, COLOR_MENU, COLOR_MENUTEXT, COLOR_SCROLLBAR, COLOR_WINDOW, COLOR_WINDOWFRAME, COLOR_WINDOWTEXT.
Zu diesem Farbwert wird 1 addiert und das
Ergebnis
in HBRUSH umgewandelt.
Zur Abwechslung wollten wir Ihnen einmal ein
schwarzes
Fenster bieten, daher:
(HBRUSH) (COLOR_WINDOWTEXT + 1).
Testen Sie bitte einmal (HBRUSH) (0),
damit
Sie verstehen, warum man 1 addieren muß.
Das sind doch interessante Transparenz-Effekte?
Jedoch völlig daneben.
Wenn Sie gerne ein graues Fenster möchten,
wie
in Dialoganwendungen üblich, dann ist (HBRUSH) (COLOR_3DFACE + 1)
oder
(HBRUSH) (COLOR_BTNFACE + 1) der richtige Wert
für
Ihr Fenster.
Die übliche Standardeinstellung ist natürlich: (HBRUSH) (COLOR_WINDOW + 1).
Sie können jedoch ebenso eine eigene Hintergrundfarbe generieren, in dem Sie ein eigenes Objekt der MFC-Klasse CBrush erzeugen. Untersuchen Sie folgendes Beispiel:
#include
"EinfachesFenster.h"
CMyApplication
MyApp;
CBrush
brushMyBackground(
RGB( 100, 0, 100 ) );
CMyWindow::CMyWindow()
{
CString
strWndClass =
AfxRegisterWndClass
(
CS_HREDRAW | CS_VREDRAW,
MyApp.LoadStandardCursor( IDC_ARROW ),
brushMyBackground,
MyApp.LoadStandardIcon( IDI_WINLOGO )
);
Create(
strWndClass, _T( "MFC-Anwendungsskelett" ) );
}
Hier sollten Sie sehr genau hinschauen. Wir haben CBrush brushBackground als globales Objekt, also außerhalb der Klassen, erstellt. Sie finden es in der Klassenansicht daher an der gleichen Stelle wie MyApp. Wenn Sie also einmal globale Variablen, Objekte oder Funktionen erstellen wollen, ist das alleinige Anwendungsobjekt ein guter Wegweiser. Im Sinne der Objektorientierung ist es jedoch besser, möglichst viele Objekte den vorhandenen Klassen zuzuordnen. Daher werden wir unseren "Hintergrund-Pinsel" der Klasse CMyWindow als Member-Variable zuordnen. Die Deklaration erfolgt in der Header-Datei. Wir stellen ein m_ voran, um sie eindeutig als Member-Variable zu kennzeichnen:
class
CMyWindow
: public CFrameWnd
{
public:
CBrush
m_brushMyBackground;
CMyWindow();
afx_msg
void OnLButtonDown();
afx_msg
void OnRButtonDown();
afx_msg
void OnPaint();
DECLARE_MESSAGE_MAP()
};
Im Konstruktor (Quellcode-Datei) beleben wir diese neue Member-Variable:
CMyWindow::CMyWindow()
{
m_brushMyBackground.CreateSolidBrush(
RGB(100, 0, 100 ) );
CString
strWndClass
=
AfxRegisterWndClass
(
CS_HREDRAW
| CS_VREDRAW,
MyApp.LoadStandardCursor(IDC_ARROW),
m_brushMyBackground,
MyApp.LoadStandardIcon(
IDI_WINLOGO )
);
...
Die Funktion CBrush::CreateSolidBrush( Farbe ) wendet einen durchgehenden "Pinselstrich" an. Versuchen Sie auch einmal:
m_brushMyBackground.CreateHatchBrush( HS_CROSS, RGB( 240, 240, 240 ) );
CBrush::CreateHatchBrush( Hatch Style, Farbe ) gibt Ihnen die Möglichkeit verschiedene Hatch Styles einzusetzen. Weitere Details zu diesem Thema finden Sie im MSDN unter "CBrush Class Members".
Der vierte Parameter gibt uns die Gelegenheit, ein Standard-Icon oder auch ein eigenes Icon einzubinden. Hier haben wir uns zunächst für das von Windows bereit gestellte Icon IDI_WINLOGO entschieden. Ein unauffälliges Standard-Icon erhalten Sie, wenn Sie IDI_APPLICATION verwenden. Zusätzlich stehen Ihnen auch die Message-Box-Icons zur Verfügung: IDI_HAND, IDI_QUESTION, IDI_EXCLAMATION und IDI_ASTERISK.
Ihnen reicht dies nicht aus? Sie wollen eigene Icons erstellen und anzeigen? Gut, dann müssen wir zunächst eine eigene Ressource erzeugen (Bisher haben Sie nur die Klassen- und Dateiansicht gesehen).
Wählen Sie im Menü: Einfügen /
Ressource (Strg + R) / Icon / Neu. Zeichnen Sie jetzt ein
eigenes
Icon und speichern Sie es ab. Als Name geben Sie "EinfachesFenster"
ein.
Jetzt wählen Sie über Projekt / Dem Projekt hinzufügen /
Dateien die Datei
EinfachesFenster.rc
und resource.h aus.
Anschließend sehen Sie im Arbeitsbereich-Fenster auch die Auswahl
"Ressourcen". Der serienmäßige Bezeichner für das erste
Icon ist IDI_ICON1. Wenn Sie nun im Konstruktor
CMyWindow::CMyWindow()
umgehend MyApp.LoadIcon( IDI_ICON1 ) einfügen und
kompilieren,
erhalten Sie "error C2065: 'IDI_ICON1' : nichtdeklarierter Bezeichner",
denn wir müssen noch die vom Ressourcen-Editor
erzeugte Datei resource.h per include-Anweisung
in die Datei EinfachesFenster.h aufnehmen. Dann funktioniert es.
Nachstehend finden Sie die wesentlichen Änderungen im Überblick:
EinfachesFenster.h:
#include <afxwin.h>
#include "resource.h"
class CMyApplication : public CWinApp
...
EinfachesFenster.cpp:
#include "EinfachesFenster.h"
CMyApplication MyApp;
CMyWindow::CMyWindow()
{
m_brushMyBackground.CreateHatchBrush(
HS_CROSS, RGB( 240, 240, 240 ) );
CString strWndClass =
AfxRegisterWndClass
( CS_HREDRAW | CS_VREDRAW
| CS_DBLCLKS,
MyApp.LoadStandardCursor(
IDC_ARROW ),
m_brushMyBackground,
MyApp.LoadIcon(
IDI_ICON1 )
);
Create( strWndClass, _T(
"MFC-Anwendungsskelett"
) );
}
...
resource.h (vom Ressourcen-Editor automatisch erzeugt):
...
#define IDI_ICON1 101
...
EinfachesFenster.rc (vom Ressourcen-Editor erzeugt, Name wurde eingegeben):
...
#include "resource.h"
...
#include "afxres.h"
...
IDI_ICON1 ICON DISCARDABLE
"icon1.ico"
...
Damit haben wir nun mit einer individuellen WNDCLASS ein eigenes Fenster erzeugt und ihm eine Nachrichtenbehandlung und ein eigenes Icon spendiert. Fenster verfügen noch über eine ganze Menge von Details wie Menüs, Toolbars, Statusleisten etc. Neben der oben erzeugten SDI-Anwendung gibt es noch MDI- und dialogbasierende Anwendungen. Aufgrund der Vielfalt und Komplexität ist der Einsatz der Assistenten sinnvoll. Insbesondere der Klassenassistent ist von großem Wert.
Aus didaktischen Gründen findet man in der Literatur und in der Hilfe viele Header- und Quellcode-Dateien, die manuell erzeugt wurden. Um Teile davon in eigene mit den Assistenten generierte Anwendungen einzubinden, sollte man die manuelle Vorgehensweise verstehen. Darüber hinaus werden Sie den vom Assistenten erzeugten Code jetzt besser verstehen, aber auch kritischer betrachten.
Hier noch einmal der wesentliche Zusammenhang bei
unserem Rahmenfenster zwischen MFC-Anwendungs- und -Fensterklasse:
Abb. 5.5: Zusammenspiel von
CWinApp
und CFrameWnd
5.5 Ein winziges Windows-Programm
Für die vorstehende SDI-Anwendung wurde der
Anwendungsklasse CWinApp die Fensterklasse CFrameWnd
zur
Verfügung gestellt. Wie Sie aus früheren Kapiteln wissen, ist
dies bei Dialogfenstern die MFC-Klasse CDialog. Wenn wir die
Fenstertypen
grob einteilen, dann ergibt sich folgendes Bild:
MDI-Anwendung | CMDIFrameWnd, CMDIChildWnd |
SDI-Anwendung | CFrameWnd |
Dialog | CDialog |
Eigenschaftsseite | CPropertySheet, CPropertyPage |
Steuerelemente | von CWnd abgeleitete Klassen |
MessageBox | Member-Funktion von CWnd |
AfxMessageBox | keine
MFC-Klasse,
sondern AFX-API-Funktion |
Alle Fenster - mit Ausnahme von AfxMessageBox -
stammen
von der zentralen MFC-Klasse CWnd ab. Will man nun die Anwendungsklasse
in den Mittelpunkt rücken und damit zeigen, daß man mit MS
Windows
weniger als zehn Zeilen benötigt, um ein lauffähiges Programm
zu erstellen, das bereits ein Fenster (zählt man den OK-Button
mit,
sind es sogar zwei) ausgibt, dann setzt man die Funktion
AfxMessageBox(...)
ein, die keiner MFC-Klasse zugeordnet ist. Hier ein lauffähiges
Beispiel:
#include
<afxwin.h>
class
CMyApplication
: public
CWinApp
|
Abb. 5.6: Es geht auch mit
CWinApp
und einer AfxMessageBox
Wie Sie sehen, kommt dieses Beispiel mit nur
einer
MFC-Klasse aus, nämlich CWinApp.
Das Fenster wird als AfxMessageBox erstellt.
Hier folgt ein Praxis-Beispiel, das die
Windows-Version
ausgibt:
#include
<afxwin.h>
class CMyApplication : public CWinApp { virtual BOOL InitInstance() { CString str; str.Format( "Windows-Version: %d.%d", _winmajor, _winminor ); AfxMessageBox( str, MB_ICONINFORMATION, 0 ); return TRUE; } }; CMyApplication MyApp; |
Wer es einmal besonders schrill mag, dem sei
folgende
Programmvariante zur Anschauung empfohlen:
#include
<afxwin.h>
class CMyApplication : public CWinApp { virtual BOOL InitInstance() { AfxAbort(); return TRUE; } }; CMyApplication MyApp; |