Die WinAPI Plattform

Tutorials

Tutorial Nummer 17

(17.) Editboxen erstellen und benutzen

Hi, dieses Tutorial ist ausnahmsweise mal nicht von Henno Buschmann, sondern von mir - Johannes Löwe (Noil). Wir haben uns aber darauf geeinigt, den gleichen Stil zu benutzen (d.h. ich seinen), also dürfte es keine Verständnisprobleme geben.

Dieses Tutorial behandelt Editfelder, die du sicher aus vielen Anwendungen kennst. Fast jedes einzeilige und viele mehrzeilige Eingabefelder nutzen diese bereits registrierte Fensterklasse. Das Beispielprogramm soll eine Minitexteditor werden, der beim Start den Inhalt des Editfeldes aus einer bestimmten Datei liest, und am Ende den Inhalt wieder in diese Datei schreibt. Deshalb brauchen wir für die Standart C Datei Funktionen die stdio.h und für die dynamische Speicherverwaltung die stdlib.h.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
    HWND     hWnd;
    MSG      msg;
    WNDCLASS wc;

    const char szAppName[] = "Editcontrol Tutorial";

    wc.style          = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc    = WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hInstance      = hInstance;
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground  = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = szAppName;

    RegisterClass(&wc);

    hWnd = CreateWindow( szAppName,
                         "Editcontrol Tutorial",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         NULL,
                         NULL,
                         hInstance,
                         NULL );

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);

    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

Das Parentfenster wurde analog zu den vorhergehenden Tutorials erstellt. Genau wie im "Button Tutorial" brauchen wir noch ein Handle für das Editfeld um darauf zugreifen zu können.

    static HWND hEdit;

    switch(message)
    {
    case WM_CREATE:
        {

Als nächstes kommt der Code zum lesen des Textes aus einer Datei. Dazu wird nach dem Öffnen der Datei zunächst die Größe bestimmt, um einen entsprechend großen Speicherbereich anzufordern. Dann kann der Inhalt der Datei in den Speicher gelesen werden.

            FILE *fz;
            char *buffer = NULL;
            int iFileSize;

            fz = fopen("text.txt", "rb");
            if(fz != NULL)
            {
                fseek(fz, 0, SEEK_END);
                iFileSize = ftell(fz);
                buffer = malloc(iFileSize);

                fseek(fz, 0, SEEK_SET);
                fread(buffer, 1, iFileSize, fz);
                fclose(fz);
            }

Wer sich nicht mit den C Standard Funktionen auskennt und/oder in C++ programmiert, kann an dieser Stelle auch die entsprechenden Streamklasse benutzen. Ich möchte außerdem darauf hinweisen, dass die WinApi selbst auch Funktionen zur Dateiverwaltung bereitstellt. Da sie aber sehr komplex sind wird es über sie später vermutlich ein eigenes Kapitel geben.

Jetzt wird das Editfeld erstellt. Dazu wird die neue Funktion CreateWindowEx (MSDN: CreateWindowEx) benutzt. Sie verwendet im vergleich zu CreateWindow ein Parameter mehr. Über dieses Parameter können dem Fenster erweiterte Styles zugewiesen werden. In diesem Fall ist das ein tiefer Rahmen, wie ihn Editfelder normalerweise immer haben. Die anderen Parameter sind nur um eins nach hinten verschoben, sonst aber identisch. Als Fensterklasse wird - wie zu erwarten war - "edit" angegeben. Außerdem werden zu den ohnehin nötigen Styles WS_CHILD und WS_VISIBLE noch Styles für eine vertikale Scrollbar, ein mehrzeiliges Editfeld und das automatische vertikal Scrollen hinzugefügt (MSDN: Edit Control Styles). Bevor wir dann die Nachrichtenprozedur beenden muss jedoch der angeforderte Speicher wieder freigegeben werden.

            hEdit = CreateWindowEx(WS_EX_CLIENTEDGE,
                                   "edit",
                                   buffer,    // <- das ist der Inhalt der Editfelds
                                   WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE |
                                              ES_AUTOVSCROLL,
                                   0, 0, 0, 0,
                                   hWnd,
                                   NULL,
                                   ((LPCREATESTRUCT) lParam) -> hInstance,
                                   NULL);

            free(buffer);

            return 0;
        }
    case WM_SIZE:
        {

Damit das Editfeld immer die gesamte Clientarea des Parentfensters ausfüllt, wird es bei einer Größenänderung des Parentfensters mit angepasst.

            MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return 0;
        }
    case WM_CLOSE:
        {

Bei den bisherigen Programmen wurde die Nachricht WM_CLOSE nur zum Beenden des Programms gesendet, jetzt behandeln wir diese Nachricht selbst. Sie wird von Windows an das Programm gesendet, wenn eine der Standardmethoden zum Beenden (z.B. Klick auch das Kreuz rechts oben, oder ALT+F4) ausgeführt wird. Da der Text des Editfeldes gespeichert werden soll, müssen wir WM_CLOSE dazu nutzen, da bei WM_DESTROY das Fenster bereits zerstört wäre.

Wenn also das Fenster geschlossen werden soll, lesen wir mit GetWindowTextLength (MSDN: GetWindowTextLength) die Länge des im Editfeld befindlichen Textes ein und fordern ausreichend Speicher an, um den Text des Editfeldes speichern zu könen. Als nächstes wird mit GetWindowText (MSDN: GetWindowText) der Inhalt des Editfeldes erst in den Speicher und dann in die Datei geschrieben. Auch hier muss der angeforderte Speicher wieder freigegeben werden.

Da wir WM_CLOSE nun selbst abgefangen haben müssen wir auch selbst dafür sorgen, dass das Fenster zerstört wird. Dazu gibt es mehrere Möglichkeiten. Entweder zerstört man das Fenster mit DestroyWindow (MSDN: DestroyWindow) selbst, oder man ruft DefWindowProc auf. Das geht am einfachsten, indem man mit break; aus der switch-Konstruktion springt, wodurch die return-Anweisung am Ende der Nachrichtenprozedur ausgeführt wird. Bei WM_DESTROY kommt dann wie gewohnt PostQuitMessage hin.

            FILE *fz;
            char *buffer = NULL;
            int iLength;

            iLength = GetWindowTextLength(hEdit);

            buffer = malloc(iLength);

            GetWindowText(hEdit, buffer, iLength+1);

            fz = fopen("text.txt", "wb");
            fwrite(buffer, 1, iLength, fz);
            fclose(fz);

            free(buffer);

            DestroyWindow(hWnd);
            return 0;
        }
    case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

Jetzt kannst du das Programm compilieren und dir das Ergebnis ansehen (Screenshot).

Übungen

  1. Füge zwei weitere einzeilige Editfelder und zwei Buttons ("Laden"/"Speichern") in das Programm ein
  2. Beschrifte nun die beiden Editfelder mit "Datei" und "Passwort". Das Passwort-Editfeld soll nur Zahlen akzeptieren und diese wie du es sicher schon gesehen hast als Sternchen anzeigen. Bei Klicken auf "Speichern" soll der Inhalt des Editfeldes in der Datei gespeichert werden, die im Datei-Editfeld angegeben wurde, aber nur wenn das Passwort 123456 war. Bei Klicken aus "Laden" soll analog der Text aus der im Datei-Editfeld angegebenen Datei geladen und angezeigt werden. Dies aber nur, wenn das Passwort 654321 war.

Anmerkung: Das ganze ist einfacher als du vielleicht denkst. Ich habe noch einen Tipp, der hilfreich sein könnte: Du brauchst für die Eigenschaften des Passworteditfelds keine eigenen Funktionen!!

Kritik und Lob geht diesmal an mich (noil@win-api.de) und ich freue mich natürlich genauso darüber wie Henno!

Johannes Löwe (Noil)

webmaster@win-api.de