erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC   ( Stand: 31.05.2002 )

Zurueck zum Inhaltsverzeichnis

zurück zum vorherigen Kapitel
 

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
{
 public:
 virtual BOOL InitInstance();
};

class CMyWindow : public CFrameWnd  //Schritt 3
{
 public:
 CMyWindow();
};

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
{
 Create ( NULL, _T("MFC-Anwendungsskelett") );
}

BOOL CMyApplication::InitInstance() //Schritt 7
{
 m_pMainWnd = new CMyWindow;
 m_pMainWnd ->ShowWindow( m_nCmdShow );
 return TRUE;
}


 

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

  virtual BOOL InitInstance()
    { 
      AfxMessageBox( "hello, world" );
      return TRUE;
    }
};
CMyApplication MyApp;

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;

 

Weiter zum Nächsten Kapitel

Zurueck zum Inhaltsverzeichnis