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).
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)