Seku
HomeProjekteTutorialsToolsGFXLinksForumGästebuchKontaktImpressumLogin
Stoppt die Vorratsdatenspeicherung - www.vorratsdatenspeicherung.de

tutorial anzeigen

Durch lesen dieses Artikels akzeptieren Sie folgende Nutzungsbedingungen:
Dieser Artikel und alle verwendeten Materialien (Bilder, Quelltexte, etc.) unterliegen dem Copyright des Autors, hier Seku. Das kopieren dieses Artikels, Auszügen oder verwendeten Materialien und Einfügen auf anderen Seiten als dieser (seku.info) ist nur mit einer ausdrücklichen, schriftlichen Einverständniserklärung des Autors erlaubt. Weder Autor noch der Betreiber dieser Seite haften für eventuelle Schäden, die durch diesen Artikel oder herunterladbare Objekte ( > Downloads ) verursacht wurden. Die Richtigkeit dieses Textes ist nicht garantiert.

Einleitung

Guten Tag,

In diesem Tutorial will ich euch erklären, wie man bei Windows Fenster erstellt. Leuten, die sich auf diesem Gebiet schon ein bisschen auskennen, sei gesagt, dass wir keine Dialog und auch keine MFC verwenden werden. Ich beginne am besten mit einer Einführung in die GUI (Grafische Benutzerobefläche) von Windows. Zunächst sollte man wissen, dass die komplette WindowsGUI auf Fenster aufgebaut ist. Alles sind Fenster, nicht nur die Fenster wie man sie als Benutzer kennt, sondern auch Knöpfe und Eingabefelder sind Fenster.
Dann sollte man wissen, dass die WinApi auf Handles aufgebaut ist. Man erkennt sie am "H" als erstes Zeichen. Als Beispiel wären da HWND, als ein Handle auf ein Fenster, HICON, als Icon, HCURSOR, als Cursor, und viele mehr. Handles sind Variablen auf die Elemente.
Nochmal zurück zu den Fenstern; Da Fenster untereinander und auch zu Windows und dem Benutzer kommunizieren müssen, gibt es Nachrichten. Jedes Fenster hat eine Nachrichtenfunktion, welche bei jeder Interaktivität aufgerufen wird. Das kann z.B. sein, dass der Benutzer einen Knopf drückt oder ein anderen Eintrag in einer Liste ausgewählt hat. Der Einfachheit halber werden Nachrichten von den Elementen (also Knöpfe, Checkboxen, etc.) nicht nur an ihre eigene Nachrichtenfunktion, sondern auch an die des zugehörigen Fensters geschickt. Die Nachrichtenfunktion nennt man auch Rückruffunktion
Ich denke, jetzt wissen wir schon viel über die GUI von Windows und wir können nun ans Programmieren gehen . Davor aber noch eins. Ich empfehle euch wirklich das Beispiel anzusehen(!)

Die Fensterklasse

Bevor wir ein Fenster erstellen können, müssen wir erstmal seine Fensterklasse bei Windows registrieren. In dieser Klasse stehen Angaben wie das Icon in der oberen linken Ecke, den verwendeten Cursor, die Hintergrundfarbe, usw. Die Struktur einer Fensterklasse heißt WNDCLASS. Wir werden aber die etwas andere Struktur WNDCLASSEX benutzen. Schauen wir uns mal ihre Definition an

struct WNDCLASSEX
{
    unsigned int        cbSize;         // Die Größe der Struktur in Bytes
    unsigned int        style;          // Style Flags des Erscheinungsbildes
    WNDPROC             lpfnWndProc;    // Funktionspointer auf die Rückruffunktion (siehe unten)
    int                 cbClsExtra;     // Extradaten der Klasse (für uns 0)
    int                 cbWndExtra;     // Extradaten des Fensters (für uns 0)
    HINSTANCE           hInstance;      // Handle auf das Programm (bekommt man von WinMain())
    HICON               hIcon;          // Icon des Fensters
    HCURSOR             hCursor;        // Der Cursor des Fensters
    HBRUSH              hbrBackground;  // Pinsel für den Hintergrund
    char*               lpszMenuName;   // Der Name des Menüs für das Fenster
    char*               lpszClassName;  // Der Name der Fensterklasse
    HICON               hIconSm;        // Die verkleinerte Version von hIcon
};

Die Variable style beinhaltet diverse Klassenstyles, welche man wie Flags kombinieren kann. Wir werden hier CS_CLASSDC benutzen. Wer sich für dafür interessiert, kann mal ein Blick in die MSDN werfen. Was vielleicht auch nützlich sein könnte ist CS_NOCLOSE, bei welchem kein Schließsymbol oben rechts im Fenster ist.

Für hIcon und hCursor gibt es zwei nützliche Funktionen, welche uns die Standardicons /-cursor liefern. Sie heißen LoadIcon() bzw. LoadCursor(). Bei Beiden ist der erste Parameter das Handle auf das Programm (HINSTANCE). Der Zweite ist der Name des gewünschten Icons / Cursors. Mit dem Makro MAKEINTRESOURCE() bekommen wir diesen.
Für hbrBackground gibt es auch Standardpinsel. Die Funktion GetStockObject(int iObject); liefert uns diese. Hier sind einige Möglichkeiten für iObject. Am besten man testet alle mal .
  • BLACK_BRUSH: Schwarzer Hintergrund
  • WHITE_BRUSH: weißer Hintergrund
  • GRAY_BRUSH: Dunkelgrauer Hintergrund
  • NULL_BRUSH: Kein Hintergrund (man sieht durch)
Allerdings sollte man beachten, dass es auch vordefinierte Brushe von Windows gibt. So ist z.B. COLOR_3DFACE der Standardhintergrund von Fenstern. Diese "Fertigbrushes" brauchen nicht die Funktion GetStockObject(), sonern GetSysColorBrush(). Letztendlich reichen für diese 3 Variablen folgende Definitionen.

// hInstance ist ein gültiges HINSTANCE Handle auf das Programm
WindowClass.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
WindowClass.hCursor        = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_ARROW));
WindowClass.hbrBackground  = GetSysColorBrush(COLOR_3DFACE);

Nun gut, jetzt haben wir die Fensterklasse ausgefüllt, aber Windows hat noch keinen blassen Schimmer davon. Wir müssen also die Fensterklasse noch registrieren. Dafür gibt es auch eine Funktion: BOOL RegisterClassEx(WNDCLASSEX* pWindowClass);. Was soll ich hierzu noch sagen .

Das Fenster erstellen

Jetzt können wir Fensterklasse ausfüllen und registrieren, aber irgendwas fehlt da noch.. hmm, was war das denn? ach ja.. Wir wissen jetzt immer noch nicht, wie man ein Fenster erstellt. Aber keine Angst, dafür gibt es eine Funktion

HWND CreateWindow(
           char* pcClassName,       // Der Name der Fensterklasse
           char* pcWindowName,      // Der Titel (Beschriftung der Titelleiste)
           DWORD dwStyle,           // Nochmal Styleflags (siehe unten)
           int x,                   // Die Position (X) vom oberen linken Bildschirmrand
           int y,                   // Die Position (Y) vom oberen linken Bildschirmrand
           int nWidth,              // Die Breite des Fensters
           int nHeight,             // Die Höhe des Fensters
           HWND hWndParent,         // Das übergeordnete Fenster
           HMENU hMenu,             // Das Menü
           HINSTANCE hInstance,     // Die Programminstanz
           void* pParam);           // Wird beim Erstellen bei WindowProc mitgeliefert, für uns NULL

Kommen wir zu dwStyle. Die Flags, die hier eingegeben werden können, geben Aufschluss über die Möglichkeiten mit denen der Benutzer das Fenster verändern kann. Hier folgen die paar wichtigsten
  • WS_BORDER: Das Fenster hat einen dünnen Rand
  • WS_CAPTION: Fenster mit Statuszeile
  • WS_DISABLED: Fenster ohne Eingabemöglichkeit
  • WS_DLGFRAME: Fenster im Dialogboxstyle ohne Statuszeile
  • WS_HSCROLL: Fenster mit horiziontalem Scrollbalken
  • WS_VSCROLL: Fenster mit vertikalem Scrollbalken
  • WS_OVERLAPPED: Fenster mit Titelleiste und Rahmen
  • WS_OVERLAPPEDWINDOW: Standardfenster mit Titelleiste, Rahmen, Vergrößern- und Minimierenschalftflächen und Systemmenü
  • WS_POPUP: Ein PopUp Fenster
  • WS_VISIBLE: Nach der Initialisierung sofort sichtbar (sollte man immer nehmen). Wenn man es nicht nimmt, muss man nach dem Erstellen noch ShowWindow() und UpdateWindow() aufrufen (siehe MSDN).
Die restlichen sollten keiner Erklärung bedürfen. Außer vielleicht noch hWndParent. Bei einem Element (z.B. ein Button), wäre das sein Fenster (ich weiß, Buttons sind auch Fenster, aber ihr wisst schon, was ich meine). Diese Fenster brauchen keinen Parent.

So, jetzt haben wir ein Fenster erstellt. Damit unser Fenster die Nachrichten aus der Nachrichtenwarteschlange bekommt, müssen wir Windows ständig dazu aufrufen. Die Funktion dafür heißt

GetMessage(MSG* pMessage,                 // Die Nachricht
           HWND hWnd,                     // Für uns NULL
           unsigned int wWsgFilterMin,    // Minimale Nachricht (für uns 0)
           unsigned int wWsgFilterMax);   // Maximale Nachricht (für uns 0)

Diese Funktion wartet für jedes Fenster der Programminstanz, bis eine Nachricht eintrifft sie zu jenem. Das heißt, dass wir, egal wieviel Fenster, diese Funktion nur einmal einbauen müssen. Die erste Variable ist ein Pointer auf die Nachricht, die das Fenster erhalten soll. Der zweite Parameter ist für uns NULL. Er ist nicht das Handle auf das Fenster, für welches wir Nahrichten wollen! Die letzten beiden sind für uns auch egal und deshalb 0.

Nachdem wir die Nachricht erhalten haben, müssen wir sie an das Fenster weiterleiten. Dafür brauchen wir zwei Funktionen: TranslateMessage(MSG* pMessage) und DispatchMessage(MSG* pMessage). Die Variable beider Funktionenen ist der Pointer auf die Nachricht von GetMessage(). Man sollte diese 3 Funktionen in einer Schleife laufen lassen, sodass die Fenster ständig ihre Nachrichten bekommen. Bekommt es diese nämlich nicht, wird es zwangsweise abstürzen, da es auch nicht die Befehle zum Beenden erhält .

Bevor wir zu der Rückruffunktion für die Nachrichten kommen, gibt es noch etwas zu GetMessage. Da diese Funktion wartet bis eine Nachricht eintrifft, ist sie für manche Projekte ungeeignet. Zum Beispiel bei einem Spiel kann es zu rucklern führen, wenn wir ständig warten müssen. Deshalb gibt es noch eine zweite Funktion, die dasselbe macht, mit der Ausnahme, dass sie nicht wartet, sondern guckt ob JETZT GERADE eine Nachricht da ist.

PeekMessage(MSG* pMessage,                 // Wie oben
            HWND hWnd,                     // Wie oben
            unsigned int wWsgFilterMin,    // Wie oben
            unsigned int wWsgFilterMax,    // Wie oben
            unsigned int wRemoveMsg);

Der letzte Parameter (die anderen sind gleich), entscheidet, ob die Nachrichten nach dem Funktionsaufruf aus der Nachrichtenschleife gelöscht werden. Wir geben hier PM_REMOVE an.

Die Struktur MSG
Diese Nachrichtstruktur MSG will ich noch schnell erklären.

struct MSG
{
    HWND hwnd;              // Handle zum Zielfenster
    unsigned int message;   // Die Nachricht (= uiMsg von WindowProc)
    WPARAM wParam;          // Der WParam
    LPARAM lParam;          // Der LParam
    DWORD time;             // Die Absendezeit
    POINT pt;               // Die Mausposition (Pixel), als die Nachricht verschickt wurde
};

Die Nachrichtenfunktion

Kommen wir nun zu lpfnWndProc aus der Fensterklasse. Das ist unsere Rückruffunktion. Die Struktur erwartet hier einen Funktionspointer auf folgende Funktion.

LRESULT CALLBACK WindowProc(HWND hWindow,
                            unsigned int uiMsg,
                            WPARAM WParam,
                            LPARAM LParam);

hWindow ist das Handle auf das Fenster, für das die Nachrichtenfunktion ist. uiMsg ist die Nachricht. WParam und LParam sind zwei Parameter der Nachricht.
Bei WindowProc sollte man eine Nachricht immer an Windows weiterleiten, damit auch es davon erfährt. Das geht mit der Funktion DefWindowProc(). Die Parameter sind die selben wie bei WindowProc, nur dass wir sie aufrufen und wir sie nicht definieren müssen . So könnte also eine WindowProcFunktion aussehen

LRESULT CALLBACK WindowProc(HWND hWindow,
                            unsigned int uiMsg,
                            WPARAM WParam,
                            LPARAM LParam)
{
    // Die Nachricht durchgehen
    switch(uiMsg)
    {
        // Nachrichten verarbeiten
        case 'MEINE_NACHRICHT':
            break;

        default:
            break;
    }

    // Die Nachricht an Windows weiterleiten
    return DefWindowProc(hWindow, uiMsg, WParam, LParam);
}

Kommen wir zum Parameter uiMsg. Das ist, wie bereits gesagt, die Nachricht. Es gibt sehr viele Nachrichten, eigentlich viel zu viele . Für den Anfang aber sollten diese reichen.
  • WM_ACTIVATE: Das Fenster wurde de-/aktiviert
  • WM_CLOSE / WM_DESTROY: Das Fenster wurde geschlossen (siehe unten)
  • WM_COMMAND: Ein Element (z.B. ein Knopf) wurde gedrückt. Dazu mehr im nächsten Tutorial
  • WM_CREATE: Das Fenster wurde erstellt
  • WM_MOVE: Das Fenster wurde verschoben
  • WM_SIZE: Die Größe des Fenster wurde geändert
  • WM_PAINT: Das Fenster wird gerade auf den Bildschirm gebracht
  • WM_QUIT: Die Anwendung möchte beendet werden
Noch eins zu WM_CLOSE und WM_DESTROY. Wenn eine dieser Nachrichten das Fenster erreicht, sollte man die Funktion PostQuitMessage() aufrufen. Hier der Prototyp

void PostQuitMessage(int nExitCode);

Wenn wir diese Funktion aufrufen, bekommt das Fenster die Nachricht WM_QUIT. Wir können in der Schleife immer abfragen, ob diese Nachricht ankommt und dann die Schleife abbrechen. Das Fenster wird automatisch beendet (natürlich kann man auch mit DestroyWindow(HWND); nachhelfen ). Sonst läuft die Schleife weiter und das Programm endet nie. Ich denke, das wird im Beispielprogramm gut erkennbar sein.

Ein zusammenfassendes Beispielprogramme

Was soll ich noch sagen? Es sollte eigentlich alles klar sein. Außer vielleicht, dass man WNDCLASSEX und die Nachricht MSG vor Beginn mit ZeroMemory leeren sollten, da sonst Fehler auftreten können. Aber guckt euch das Beispiel an

/********************************************************
 *                                                      *
 *                                                      *
 *    Main.cpp                                          *
 *    ========                                          *
 *    Autor: Seku                                       *
 *    Zweck: Beispiel das Erstellen von Fenstern        *
 *                                                      *
 *                                                      *
 ********************************************************/

#include <Windows.h>

// ======================================================
// Globale Variablen
HINSTANCE g_hInstance;

// ======================================================
// Die Nachrichtenfunktion
LRESULT CALLBACK WindowProc(HWND hWindow,
                            unsigned int uiMsg,
                            WPARAM WParam,
                            LPARAM LParam)
{
        // Die Nachricht durchgehen
        switch(uiMsg)
        {
                case WM_CREATE:
                        // Das Fenster wurde erstellt.
                        break;

                case WM_DESTROY:
                        // Das Fenster wird beendet
                        DestroyWindow(hWindow); // Beendet das Fenster
                        UnregisterClass("MySampleClassForMySampleWindow - Seku",
                                        g_hInstance);        // Löscht die Fensterklasse
                        PostQuitMessage(1);
                        break;

                default:
                        break;
        }

        // Die Nachricht an Windows weiterleiten
        return DefWindowProc(hWindow, uiMsg, WParam, LParam);
}

// ======================================================
// Die Hauptfunktion
int WINAPI WinMain(HINSTANCE hInstance,
                                   HINSTANCE hPrevInstance,
                                   char* pcCmdLine,
                                   int iShowCmd)
{
        WNDCLASSEX        WindowClass;                // Die Fensterklasse
        MSG               Message;                    // Die Nachricht
        HWND              hWindow;                    // Das Fenster

        // Die Programminstanz globalisieren
        g_hInstance = hInstance;

        // Die Fensterklasse erstellen
        ZeroMemory(&WindowClass, sizeof(WNDCLASSEX)); // sonst kann es zu Fehlern kommen
        WindowClass.cbSize           = sizeof(WNDCLASSEX);
        WindowClass.style            = CS_CLASSDC;
        WindowClass.lpfnWndProc      = WindowProc;
        WindowClass.cbClsExtra       = 0;
        WindowClass.cbWndExtra       = 0;
        WindowClass.hInstance        = hInstance;
        WindowClass.hIcon            = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
        WindowClass.hIconSm          = WindowClass.hIcon;
        WindowClass.hCursor          = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_ARROW));
        WindowClass.hbrBackground    = GetSysColorBrush(COLOR_3DFACE);
        WindowClass.lpszMenuName     = NULL;
        WindowClass.lpszClassName    = "MySampleClassForMySampleWindow - Seku";

        // Die Fensterklasse registrieren
        if(!RegisterClassEx(&WindowClass))
        {
                // Fehler!
                return FALSE;
        }

        // Das Fenster erstellen
        hWindow = CreateWindow(WindowClass.lpszClassName,
                               "Testfenster",
                               WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                               50, 50,
                               250, 180,
                               NULL,
                               NULL,
                               hInstance,
                               NULL);
        if(!hWindow)
                return FALSE;

        // Die Fensterschleife starten
        ZeroMemory(&Message, sizeof(MSG)); // sonst kann es zu Fehlern kommen
        while(GetMessage(&Message, NULL, 0, 0))
        {
                // Die Nachricht weiterleiten
                TranslateMessage(&Message);
                DispatchMessage(&Message);
                if(Message.message == WM_QUIT)
                {
                        // Das Fenster soll beendet werden; wir verlassen die Schleife
                        break;
                }
        }

        // Alles OK!
        return TRUE;
}

Hier könnten vielleicht noch die Funktionen UnregisterClass() und DestroyWindow() auffallen. Aber ich denke, die Parameter und die Funktion dahinter sollten klar sein.

Ausblick

So, jetzt haben wir ein Fenster. Aber irgendwie sieht es immernoch ein klein wenig leer aus. Das liegt daran, dass es leer ist (ja, wirklich!). Im nächsten Teil kommen wir dann zu den ersten Elementen. Um genauer zu sein kommen wir zu Statischen Objekten wie Bildern, Icons und Texte. Aber am Besten ihr lest es selber durch. cu Seku.




Alle Inhalte stehen, wenn nicht anders angegeben, unter dem Copyright von Seku.
Für die Inhalte externer Seiten ist der jeweilige Betreiber verantwortlich.
seku.info v.3.2, Benötigte Zeit: 0,220 Sekunde(n)
Ausgeführte mySQL-Anfragen: 0
17:06