Fensterklasse

Zurück Zum Inhaltsverzeichnis Weiter
Bevor wir mit der Beschreibung der Fensterklassen beginnen, wollen wir erst einmal dafür sorgen, dass wir in Zukunft auch die gleiche Sprache sprechen.

Wenn im Folgenden der Begriff Fensterklasse verwendet wird so sind damit die durch die MFC bereitgestellten Fensterklassen wie z.B. CWnd gemeint. Wenn Bezug auf die WINDOWS Fensterklasse (Stichwort: RegisterClassEx(...)) genommen wird, so wird dies ausdrücklich erwähnt.

Der Begriff Fenster wird immer für das durch WINDOWS verwaltete Fenster verwendet. Fensterobjekte hingegen bezeichnen C++ Objekte, welche in der Regel im Programmverlauf mit einem Fenster verknüpft werden. Diese Verknüpfung geschieht über die Membervariable m_hWnd des Fensterobjektes die das zum Fenster gehörende Fenster-Handle enthält.

Verknüpfung zwischen Fensterobjekt und Fenster

Die Klasse CWnd stellt für die Anwendung die Schnittstelle dar, über die sie das WINDOWS Fenster beeinflussen kann. D.h. die Klasse CWnd, oder eine davon abgeleitete Klasse, kapselt viele der API-Funktionen die sich mit der Fenstersteuerung befassen. Sie besitzt eine schier unüberschaubare Anzahl von Methoden zur Fenstersteuerung. Sie können sich nun eine Kurzübersicht der Klasse CWnd ansehen um einen Überblick über die wichtigsten Methoden dieser Klasse zu erhalten.

In der Regel wird für das Hauptfenster der Anwendung nicht direkt ein CWnd-Objekt verwendet sondern ein Objekt einer von CWnd abgeleiteten Klasse. Für unsere ersten Schritte verwenden wir als Hauptfenster ein Objekt der Klasse CFrameWnd. Die Klasse CFrameWnd erweitert die Eigenschaften der Klasse CWnd z.B. um eine Titelzeile oder auch um die Möglichkeit dem Fenster ein Menü hinzuzufügen.

Um das Hauptfenster einer Anwendung zu erstellen sind folgende Schritte durchzuführen:

  • Zuerst ist ein entsprechendes CFrameWnd-Objekt oder einer von CFrameWnd abgeleiteten Klasse, zu erstellen.
  • Soll ein Fenster mit einem vom Standard abweichenden Stil erstellt werden, so ist anschließend eine der Funktionen  ::AfxRegisterWndClass(...), ::AfxRegisterClass(...) oder ::RegisterClassEx(...) aufzurufen.
  • Wurde das Fensterobjekt erstellt kann das Fenster erzeugt werden. Dies erfolgt durch den Aufruf der CFrameWnd-Methode Create(...).
  • Zum Schluss muss das erstellte Fenster noch mit der CWnd-Methode ShowWindow(...) angezeigt werden.

Aber merken Sie sich folgenden wichtigen Satz:

Die alleinige Erstellung eines Fensterobjektes erstellt noch kein Fenster sondern erzeugt nur eine entsprechende Datenstruktur!

Bleibt zum Schluss nur noch die Frage, wann bzw. wo das Hauptfenster der Anwendung erstellt wird. Erinnern Sie sich an die vorherige Lektion über die Anwendungsklasse? Dort wurde die CWinApp Methode InitInstance(...) eingeführt. Und in dieser Methode wird das Hauptfenster der Anwendung erstellt.

Erstellen Sie das Fensterobjekt für das Hauptfenster immer dynamisch. d.h. mittels des new-Operators! Würde das Fensterobjekt als lokales Objekt erstellt werden, so würde das Objekt beim Verlassen der Methode zerstört werden. Dieses Zerstören des Fensterobjekte hätte dann zur Folge, dass auch das Fenster zerstört würde und die Anwendung besäße kein Fenster mehr.
Außerdem dürfen Sie auch niemals vergessen den Zeiger auf das Fensterobjekt der Membervariable m_pMainWnd des Anwendungsobjektes zuzuweisen. Denn nur über diese Membervariable wird das Fensterobjekt mit dem Anwendungsobjekt verbunden.

Fügen wir jetzt unserem Beispiel ein einfaches Hauptfenster hinzu.

Laden Sie nun das Beispiel aus der vorherigen Lektion und gehen Sie dann in die Klassenansicht. Wenn Sie vorhin alles richtig eingegeben haben sollte die Klasse CMyApp wie folgt aussehen:

Die Anwendungsklasse

Führen Sie einen Doppelklick auf die Methode InitInstance(...) im Klassenbaum aus um die Methode im Editorfenster anzuzeigen. Diese leere Methode gilt es nun auszufüllen.

Erstellen Sie in InitInstance(...) im ersten Schritt eine Hauptfenster vom Typ CFrameWnd. Die dazu notwendigen Schritte haben Sie ja weiter oben bereits kennen gelernt. Erst im nächsten Schritt werden wir dann für das Hauptfenster eine eigene Fensterklasse verwenden.

Lösung zur Fenstererstellung

BOOL CMyApp::InitInstance()
{

   
// Fensterobjekt erstellen
    m_pMainWnd = new CFrameWnd;
   
// Fehler abfangen
    if (m_pMainWnd == NULL)
    {
       
// Bei Fehler Anwendung beenden
        MessageBox(NULL,
                   "Fehler beim Erstellen des Fensterobjektes!",
                   "FEHLER",MB_OK|MB_ICONERROR);
        return FALSE;
    }
   
// Fenster erstellen
    // ACHTUNG! Typkonvertierung des Zeigers beachten

    BOOL bRetCode = ((CFrameWnd*)m_pMainWnd)->
                           Create(NULL,"Mein erstes Fenster");
   
// Fehler abfangen
    if (!bRetCode)
    {
        MessageBox(NULL,
                   "Fehler beim Erstellen des Fensters!",
                   "FEHLER",MB_OK|MB_ICONERROR);
        return FALSE;
    }

   
// Fenster anzeigen
    m_pMainWnd->ShowWindow(m_nCmdShow);

    return TRUE;
}

Zuerst wird dynamisch ein CFrameWnd-Objekt erstellt und der zurückgelieferte Zeiger in der Membervariable m_pMainWnd des Anwendungsobjektes abgelegt. Anschließend wird mittels Create(...) das Fenster erstellt und dann mit ShowWindow(...) angezeigt. Ganz einfach, wenn man's weiß!

Beachten Sie auch die Returncodes der Methode im Fehlerfall! Liefert InitInstance(...) den Wert FALSE zurück, so wird die Anwendung nach der Rückkehr aus der Methode beendet.

Ende der Lösung

Im nächsten Schritt werden wir für das Hauptfenster eine eigene WINDOWS Fensterklasse registrieren.

Erstellen Sie für das Hauptfenster eine Fensterklasse mit den folgenden Eigenschaften:
  • Fensterstil CS_VREDRAW, CS_HREDRAW und CS_DBLCLKS
  • Restliche Fensterklassen-Eigenschaften bleiben auf dem Defaultwert.

Verwenden Sie für die Registrierung der WINDOWS Fensterklasse die oben aufgeführte Funktion AfxRegisterWndClass(...). Aber Achtung! Falls Sie nachher noch ein klein wenig mit dem Fenster herumspielen, verwenden Sie auf keinen Fall den Fesnterstil CS_NOCLOSE! Sie haben sonst keine Möglichkeit, außer dem berühmten Dreifinger-Griff <STRG>-<ALT>-<ENTF>, die Anwendung zu beenden. Beachten Sie, dass Sie den Aufruf der Methoden Create(...) jetzt noch etwas anpassen müssen!

Lösung zur Fensterklasse

BOOL CMyApp::InitInstance()
{
    // Fensterobjekt erstellen
    m_pMainWnd = new CFrameWnd;
    // Fehler abfangen
    if (m_pMainWnd == NULL)
    {
      ....
    }

    // Fensterklasse fuer Hauptfenster anlegen
    LPCTSTR lpClassName =
            AfxRegisterWndClass (CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW);

    // Fenster erstellen
    // ACHTUNG! Typkonvertierung des Zeigers beachten
    BOOL bRetCode = ((CFrameWnd*)m_pMainWnd)->
                         Create(lpClassName,"Mein erstes Fenster");

    ....
}

Beachten Sie bitte, dass Sie den mittels AfxRegisterWndClass(...) erhaltenen Namen der Fensterklasse auch beim Aufruf der CFrameWnd Methode Create(...) angeben! Nur das alleinige registrieren der neuen WINDOWS Fensterklasse reicht nicht aus.

Ende der Lösung

Wenn Sie die Übung eingegeben haben, übersetzen und starten Sie das Beispiel. Sehen Sie sich dann den Fensterinhalt an! Verschieben Sie das Fenster und ändern auch einmal dessen Größe.

Was passiert hier mit dem Fenster? Nun, da wir außer dem Fensterklassenstil alle anderen Parameter auf dem Defaultwert belassen haben, haben wir ein Fenster ohne Hintergrund, d.h. das Fenster ist sozusagen transparent. Wenn Sie das Fenster verschieben, so bleibt der bisherige Inhalt weiterhin bestehen da das Verschieben eines Fensters keine WM_PAINT Nachricht auslöst. Sie wissen doch hoffentlich noch aus dem vorherigen Kapitel den Zweck dieser Nachricht? Wenn nicht, so klicken Sie hier.

Definieren wir im nächsten Schritt die restlichen WINDOWS Fensterklassen-Eigenschaften. Was uns zu unserem Glück noch fehlt ist ein Cursor- bzw. Icon-Handle und ein Handle auf den Brush, mit dem der Fensterhintergrund auszufüllen ist.

Um einen Cursor zu laden stehen unter der MFC die beiden CWinApp Methoden LoadCursor(...) und LoadStandardCursor(...) zur Verfügung. Die erste Methode dient zum Laden eines Cursors aus einer Ressource und die zweite zum Laden eines von WINDOWS vordefinierten Standard-Cursors. Genau gleich funktioniert auch das Laden des Icons. Auch hier gibt es zwei entsprechende Methoden LoadIcon(...) und LoadStandardIcon(...). Alle vier Methoden liefern ein Handle auf den geladenen Cursor bzw. auf das geladene Icon zurück. Etwas anders verhält es sich mit dem Brush-Handle für den Fensterhintergrund. Bis wir einen eigenen Brush erzeugen können, müssen Sie hier die API-Funktion GetStockObject(...) aufrufen. Dies Funktion wurde schon im vorherigen Kapitel behandelt. Sie können sich jedoch durch anklicken des Funktionsnamens nochmals eine Beschreibung der Funktion ansehen.

Da Ressourcen und Brushs erst in späteren Kapiteln behandelt werden stehen für unser Beispiel nur die Standard-Ressourcen zur Verfügung.
Erstellen Sie für das Hauptfenster nun eine WINDOWS Fensterklasse mit den folgenden Eigenschaften:
  • Fensterstil CS_VREDRAW, CS_HREDRAW und CS_DBLCLKS
  • Für den Fenstercursor ist der Cursor IDC_UPARROW zu verwenden.
  • Der Fensterhintergrund soll hellgrau dargestellt werden,
  • Als Fenstericon ist das Icon IDI_WINLOGO zu verwenden.

Lösung zur Fensterklasse

BOOL CMyApp::InitInstance()
{
    ....
    // Fensterklasse fuer Hauptfenster anlegen

    LPCTSTR lpClassName =
              AfxRegisterWndClass (CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW,
                                   LoadStandardCursor(IDC_UPARROW),
                                   (HBRUSH)::GetStockObject(LTGRAY_BRUSH),
                                   LoadStandardIcon(IDI_WINLOGO));

    // Fenster erstellen
    ....
}

Beachten Sie die Typkonvertierung beim Laden des Brush für den Fensterhintergrund!

Ende der Lösung

Wenn Sie das Beispiel nun starten erhalten Sie ein Fenster mit den gewünschten Eigenschaften.

Erweitern wir das Beispiel nun um eine eigene (C++) Fensterklasse für das Hauptfenster. Die Fensterklasse wird, wie schon erwähnt, im einfachsten Fall von der Klasse CFrameWnd abgeleitet.

Um jetzt eine neue Klasse zum Projekt hinzuzufügen, klicken Sie mit der rechten Maustaste in der Klassenansicht den Eintrag MFCBasis Klassen an. Im eingeblendeten Kontextmenü wählen Sie den Eintrag Neue Klasse... aus woraufhin wieder folgender Dialog erscheint:

Klasse für das Hauptfenster anlegen

Nehmen Sie die oben angegebenen Einstellungen vor und klicken Sie dann den OK Button an. Auch hier schließen den darauf folgenden Hinweis auf die fehlende Headerdatei über den Ok-Button. Zum Projekt wurde jetzt die Klasse CMainWnd für das Hauptfenster hinzugefügt. Die Klassendeklaration und Methodendefinitionen befinden sich in den Dateien MainWnd.h bzw. MainWnd.cpp.

Verwenden Sie jetzt als Fensterobjekt ein Objekt der soeben nur erstellten Klasse.

Lösung zur eigenen Fensterklasse

#include "stdafx.h"
#include "MyApp.h"
#include "MainWnd.h"

BOOL CMyApp::InitInstance()
{
    // Fensterobjekt erstellen
   
m_pMainWnd = new CMainWnd;
    // Fehler abfangen
    ....
}

Haben Sie auch nicht vergessen die Header-Datei für Ihre neue Fensterklasse einzubinden?

Ende der Lösung

Sie könnten das Beispiel jetzt übersetzen und starten, würden jedoch noch keinen Unterschied zum vorherigen Programm sehen. Wir werden jetzt noch etwas mit dem Fenster spielen. Wenn Sie sich am Anfang der Lektion die Kurzübersicht zur Fensterklasse einmal angeschaut haben, so werden Sie dort u.a. die Methode PreCreateWindow(...) finden. Die Methode PreCreateWindow(...) wird durch den MFC-Rahmen aufgerufen bevor das WINDOWS Fenster erstellt wird. Durch Überschreiben der Methode und modifizieren der Elemente der übergebenen Struktur vom Typ CREATESTRUCT  können einige Eigenschaften des neuen Fensters noch angepasst werden. Die übergebene Struktur wurde durch den MFC-Rahmen bereits initialisiert, so dass Sie 'nur' noch die Elemente anpassen müssen, die Sie auch verändern wollen.

Fügen Sie zunächst über den Klassen-Assistenten zur Hauptfensterklasse CMainWnd die Methode PreCreateWindow(...) hinzu. Stellen Sie dann innerhalb dieser Methode folgende Fensterparameter ein:
  • Das Fenster soll auf dem Desktop an der Position (200/100) erscheinen.
  • Die Fenstergröße soll 300x300 Pixel betragen.
  • Das Fenster soll nur einen einfachen Rahmen besitzen.

Wenn Sie sich nicht mehr sicher sind wie Sie den Fensterrahmen beeinflussen können, klicken Sie hier. Haben Sie den Fensterstil richtig gesetzt, so kann das Fenster nachher nicht mehr in seiner Größe durch Ziehen am Rahmen verändert werden.

Lösung zur PreCreateWindow(...) Methode

BOOL CMainWnd::PreCreateWindow(CREATESTRUCT &cs)
{
   
// Fensterposition festlegen
    cs.x = 200;
    cs.y = 100;
   
// Fenstergroesse festlegen
    cs.cx = 300;
    cs.cy = 300;
   
// Dicken Rahmen zuerst entfernen!
    cs.style &= ~WS_THICKFRAME;
   
// und dann normalen Rahmen setzen
    cs.style |= WS_BORDER;
   
// Basismethode muss hier nicht aufgerufen werden
    // das fuer das Fenster eine eigene WINDOWS Fensterklasse
    // erstellt wurde.

    return TRUE;
}

Nun, die Schwierigkeit bei dieser Übung bestand im Setzen des richtigen Fensterstils. Standardmäßig wird ein Fenster unter anderem mit dem Stil WS_THICKFRAME erstellt damit das Fenster durch Ziehen am Rahmen in seiner Größe verändert werden kann. Diesen Stil müssen Sie natürlich zunächst entfernen bevor Sie einen neuen Rahmenstil setzen.

Beachten Sie auch, dass die Methode hier nicht mehr die Basisklassen-Methode aufruft da für das Fenster eine eigene WINDOWS Fensterklasse erstellt wurde. Haben Sie dies trotzdem getan, so erhalten Sie ein Fenster mit dem erweiterten Fensterstil WS_EX_CLIENTEDGE.

Ende der Lösung

Machen wir noch ein letztes 'Spielchen' mit unserem Fenster. In manchen Fällen kann es sinnvoll sein das Fenster zentriert auf dem Desktop anzuzeigen. Dazu muss aber zuerst die Größe des Desktop bekannt sein. Diese Daten liefert die API-Funktion GetSystemMetrics(...). Über den Parameter dieser Funktion können sie fast alle metrischen Daten des Systems abfragen.

Schreiben Sie nun die Methode PreCreateWindow(...) so um, dass das Fenster zentriert auf dem Desktop erscheint. Zusätzlich soll das Fenster halb so groß wie der Desktop werden.
Ich zeige Dir die Lösung! Auch hier kann ich Ihnen meine Lösung anzeigen.

Lösung zur Zentrierung des Fensters

BOOL CMainWnd::PreCreateWindow(CREATESTRUCT &cs)
{

    int nDTWidth = ::GetSystemMetrics(SM_CXSCREEN);
    int nDTHeight = ::GetSystemMetrics(SM_CYSCREEN);
   
// Fensterposition festlegen
    cs.x = nDTWidth>>2;
    cs.y = nDTHeight>>2;
   
// Fenstergroesse festlegen
    cs.cx = nDTWidth>>1;
    cs.cy = nDTHeight>>1;
    // Dicken Rahmen zuerst entfernen!
    cs.style &= ~WS_THICKFRAME;
    ....
}

War doch gar nicht schwer, oder?

Ende der Lösung

Das fertige Beispiel finden Sie im Programmverzeichnis unter 03EPMMFC\MFCBasis.

So, in der nächsten Lektion werden wir uns ein vom Anwendungs-Assistenten erstellte Rahmenprogramm ansehen.

Zurück Zum Inhaltsverzeichnis Weiter


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