IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Windows - Windows Programmierung



Windows Programmierung

Dies ist kein Kurs in Windows-Programmierung, sondern ein paar Stichworte zum Thema.


Autor: Arnold Willerner (willemer)
Datum: 23-01-2002, 19:37:05
Referenzen: http://www.willemer.de/informatik/index.htm
Schwierigkeit: Profis
Ansichten: 9480x
Rating: 7.33 (3x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Windows-Programmierung: das Grundgerüst

Und wieder einmal ein Rahmenprogramm für ein einfaches Windowsprogramm. Diesmal ist es mit Hyperlinks ausgestattet, so daß man zwischen Erklärung und Source hin- und herspringen kann. Vielleicht veranschaulicht das etwas, zumindest macht es Spaß.

MainWin

MainWin heißt das Hauptprogramm unter Windows. Per Parameter erhält es seine Instanz, auf der es seine Initialisierung aufsetzt. Es registriert die Fensterklasse, erzeugt das Hauptfenster und läuft in eine Schleife, in der es alle Messages verteilt, bis die "Null-Message" die Schleife beendet. Dies führt das zum Ende des Programms.

Die Instanz

Per Parameter erhält MainWin die Instanz von Windows. Diese Variable wird bei Aufrufen von diversen Funktionen benötigt, die an Windows gehen. An dieser Variablen erkennt Windows die Applikation wieder, auch dann, wenn sie kein Fenster geöffnet hat. Aus diesem Grund wird sie von den meisten Windowsprogrammen in einer globalen Variable gespeichert und direkt zugegriffen.

MyRegisterClass

Das Hauptfenster wird registriert und bei Windows angemeldet. Die Registrierung ist die Anmeldung einer Fensterklasse. Es können also auch mehrere Fenster der gleichen Klasse angemeldet werden.

Der RegisterClass-String

Welches Fenster zu welcher Klasse gehört, wird nicht durch ein Handle, sondern durch eine Zeichenkette beschrieben. Da diese Zeichenkette exakt identisch sein muß, wird dieser String zu Anfang in einem define-Statement festgelegt oder sogar in den Ressourcen abgelegt. Wichtig ist, daß er das Fenster im laufenden System eindeutig bezeichnet. Er wird einmal bei der Registrierung der Klasse und dann noch einmal beim Erzeugen des Fensters verwendet.

Festlegen der Eigenschaften

Das Registrieren der Klasse gibt Windows bekannt, welche Eigenschaften die Fenster dieser Klasse haben werden. Die wichtigste Information ist dabei die Adresse der Fensterfunktion WndProc. Da die Fensterklasse auf alle Ereignisse reagiert, bestimmt sie maßgeblich das Verhalten des Fensters.

InitMainWindow

In InitMainWindow wird das Fenster erzeugt (create) und angezeigt. In vielen Beispielen wird diese Funktion als InitInstance benannt.

Das Fenster wird durch den RegisterClass-String als zu der registrierten Klasse gehöriges Fenster erzeugt.

Als zweiten String hat das Fenster szTitle. Das ist der Text, der im blauen Balken zu sehen ist. Er hat nur informellen Charakter und könnte auch leer sein. Da dieser String oft sprachenabhängig ist, würde es Sinn machen, ihn in der Ressource abzulegen.

Die Fensterfunktion

Die Fensterfunktion behandelt diejenigen Ereignisse, die auf das Fenster einstürmen. Die Funktion wird von Windows gerufen, wenn immer ein Ereignis für dieses Fenster vorliegt.

Windows liefert vier Parameter mit. Der erste ist das Handle (HWND) des Fensters Dies ist besonders wichtig, wenn es mehrere Fenster gleichen Typs gibt. Der zweite Parameter ist der Typ der Message. Typischerweise besteht eine Fensterfunktion in erster Linie aus einer großend Fallunterscheidung über diese Variable. Die beiden restlichen Variablen sind Parameter des Typs. So liefert die WM_SIZE in diesen Parametern die neue Ausdehnung des Fensters.

Die Default-Fensterfunktion DefWindowProc

Werden Nachrichten nicht behandelt oder werden nur Teilaspekte des Ereignisses verarbeitet, so ruft die Fensterfunktion die Funktion DefWindowProc und übergibt ihr die Parameter, die sie von Windows erhalten hat. DefWindowProc ist die Default-Fensterfunktion und behandelt alle Ereignisse mit der Standardreaktion. So wird beispielsweise das Fenster vergrößert oder mit der Hintergrundfarbe weiß versehen.

Das Menü

Definition

In der Ressource wird das Menü beschrieben

Anmeldung

Das Menü wird beim Registrieren der Fensterklasse dem Fenster zugeordnet. Dazu wird dem Element lpszMenuName der Fensterklassenstruktur der in der Ressource definierte Namen des Menüs zugewiesen.

Ereignis der Menüauswahl

Das Anklicken eines Menüpunktes löst eine Nachricht WM_COMMAND aus, die in der Fensterfunktion verarbeitet wird. Welcher Menüpunkt ausgewählt wurde, ist im niedrigeren Teil des ersten Parameters wParam kodiert. Für das Extrahieren stellt Windows das Makro LOWORD zur Verfügung.

Der Fensterinhalt

Alles, was im Fenster gezeichnet oder angezeigt werden soll, wird nicht direkt auf das Fenster gezeichnet. Das hätte den Nachteil, daß alles verschwunden ist, wenn das Fenster kurzzeitig überdeckt wurde. Ein Fenster erhält in dem Falle, daß der Inhalt neu zu zeichnen ist, die Nachricht WM_PAINT. Hier muß es seinen Inhalt neu darstellen. Den gleichen Mechanismus benutzt man, wenn man im Fenster zeichnen will. Man manipuliert die Daten, aus denen die Fensterfunktion das Fenster rekonstruiert und ruft die Funktion Invalidate auf, und täuscht so dem Fenster vor, daß eine Neuzeichnung notwendig sei.

Beenden einer Windows-Anwendung

Der Menüpunkt für das Ende der Applikation ruft WindowDestroy auf. Dies erzeugt wiederum die Nachricht WM_DESTROY an das eigene Fenster. Hier erst setzt die Anwendung den Befehl PostQuitMessage(0); ab, das wiederum eine Nachricht absetzt. Das "Post" führt aber im Gegensatz zu "Send" dazu, daß an dieser Stelle nicht auf die Abarbeitung gewartet wird.

Die Quit-Nachricht wird nicht in der Fensterfunktion bearbeitet, sondern bewirkt ein Unterbrechen der Main-Loop und führt so zum Ende der Applikation.

Der scheinbare Umweg über das Nachrichtensystem führt dazu, daß alle beteiligten Komponenten informiert sind, daß dieses Programm sich abbaut.

Der Source


#include <windows.h>
#include "resource.h"

HINSTANCE hInst;
#define szTitle "Fenstertitel"
#define szWindowClass "MyWindowClass"

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;

switch (message) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDM_EXIT:
DestroyWindow(hWnd);
break;
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// do the painting of the window
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {0};

wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_RAHMEN);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDC_RAHMEN;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex);
}

BOOL InitMainWindow(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd) return FALSE;

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HACCEL hAccelTable;

MyRegisterClass(hInstance);
hInst = hInstance;

if (!InitMainWindow (hInstance, nCmdShow)) return FALSE;

hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_RAHMEN);

while (GetMessage(&msg, NULL, 0, 0)) {
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}

Die Resourcen


#include "resource.h"

#include "windows.h"

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.

IDI_RAHMEN       ICON    DISCARDABLE     "Rahmen.ICO"
IDI_SMALL        ICON    DISCARDABLE     "SMALL.ICO"


IDC_RAHMEN MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit",                IDM_EXIT
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About ...",           IDM_ABOUT
    END
END

IDC_RAHMEN ACCELERATORS MOVEABLE PURE
BEGIN
    "?",            IDM_ABOUT,              ASCII,  ALT
    "/",            IDM_ABOUT,              ASCII,  ALT
END

STRINGTABLE DISCARDABLE 
BEGIN
   IDC_RAHMEN    "RAHMEN"
   IDS_APP_TITLE "Rahmen"
   IDS_HELLO     "Hello World!"
END

Die Headerdatei der Ressourcen


#define IDR_MAINFRAME		128
#define IDD_RAHMEN_DIALOG	102
#define IDS_APP_TITLE		103
#define IDM_ABOUT		104
#define IDM_EXIT		105
#define IDS_HELLO		106
#define IDI_RAHMEN	        107
#define IDI_SMALL		108
#define IDC_RAHMEN	        109
#define IDC_MYICON		2
#define IDC_STATIC	        -1

Windows Programmierung: Ressourcen

Ressourcen werden heutzutage fast nur noch mit grafischen Editoren bearbeitet. Sie sind aber im Grunde ASCII-Quelltexte, die mit jedem einfachen Editor nachbearbeitet werden können. Bedauerlich ist lediglich, dass wenn einmal ein Fehler in die Ressource Quelldatei gekommen ist, der Ressouce-Compiler meist keine grosse Hilfe darstellt. Insofern sollte man Änderungen von Hand sehr vorsichtig und nie ohne Backup vornehmen.

Strings aus Ressourcen laden

Der String wird in einem Stringtable in der Ressource definiert. Der Zweck dieses Tuns liegt darin, daß internationale Versionen allein durch Änderung der Ressourcen erstellbar sind.

char Message[80];
  LoadString(GlobalInstance, ResNr, Message, sizeof(Message));

Bitmap aus Ressourcen laden

Mit der Funktion LoadBitmap kann eine Bitmap in der Ressource geladen werden. Als Ergebnis bekommt man das HBITMAP. Im Beispiel, das aus einer WM_PAINT-Bearbeitung stammt, wird der Hintergrund mit der Bitmap gekachelt.


	HBITMAP hBitmap;
	HBRUSH hbr, hbrPrevious;
	RECT rc;

	hBitmap = LoadBitmap(hInst,MAKEINTRESOURCE
(IDB_BITMAP1)); hbr = CreatePatternBrush(hBitmap); hbrPrevious = (HBRUSH)SelectObject(hdc, hbr); GetClientRect(hWnd, &rc);
PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
PATCOPY); SelectObject(hdc, hbrPrevious); DeleteObject(hbr); DeleteObject(hBitmap);

Die Bitmap muss anschliessend per DeleteObject freigegeben werden.

Windows Programmierung: Menü

Die Ressourcendefinition


IDC_UTIL MENU DISCARDABLE 
BEGIN
    POPUP "&Datei"
    BEGIN
        MENUITEM "Ö&ffnen...",                  IDM_OPEN
        MENUITEM "&Speichern als...",           IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "&Beenden",                    IDM_EXIT
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About ...",                  IDM_ABOUT
    END
END

Dass ein Fenster ein Menü hat, wird bei der Registrierung der Fensterklasse angegeben.

Empfangen eines Menüereignisses

In der Fensterfunktion wird die Nachricht WM_COMMAND empfangen. Am einfachsten unterscheidet man die Menüpunkt, indem man das niedrigere Halbwort des wParamters verwendet. Hier steht dieselbe ID, die bei der Ressourcendefinition verwendet worden ist.


    case WM_COMMAND:
        Id = LOWORD(wParam); 
        switch (Id) {
        case IDM_ABOUT:
            break;
        case IDM_OPEN:
            break;

Haken an das Menü setzen

Im Fenster des Menüs ruft man die Funktion CheckMenuItem. Im letzten Parameter muss MF_CHECKED oder MF_UNCHECKED übergeben werden, je nachdem, ob man einen Haken will oder nicht. Es reicht nicht, dort 0 oder 1 zu übergeben. Hängt der Haken von einem booleschen Wert ab, geht man wie folgt vor, um das Menü-Item IDM_KONTEXTINDEX umzuschalten:


CheckMenuItem(GetMenu(hWnd), IDM_KONTEXTINDEX, BooleWert?MF_CHECKED:MF_UNCHECKED);
BooleWert = !BooleWert;

Um das Menü-Handle zu bekommen, ruft man GetMenu mit dem HWND des Fensters auf, das das Menü enthält.

Menüpunkt deaktivieren

Im Fenster des Menüs ruft man die Funktion EnableMenuItem. Im letzten Parameter muss MF_ENABLED oder MF_GRAYED übergeben werden. Es gibt auch ein MF_DISABLED, das auch den Menüpunkt deaktiviert. Allerdings liefert dies keine optische Rückmeldung. In den allermeisten Fällen macht dies aber keinen Sinn.


EnableMenuItem(GetMenu(hWnd), IDM_SAVE, MF_GRAYED);
...
EnableMenuItem(GetMenu(hWnd), IDM_SAVE, MF_ENABLED);

Windows Programmierung: Kontextmenü

Das Kontextmenü erscheint auf den Klick mit der rechten Maustaste.

Die Ressourcendefinition


POPUPMENU MENUEX 
CHARACTERISTICS 1
{
 POPUP "Dies ist ein Test", 123
 {
  MENUITEM "&Erledigt", PM_ERLEDIGT
  MENUITEM "&Neuer Arbeitsgang", PM_NEWTASK
  MENUITEM SEPARATOR
  MENUITEM "E&igenschaften", PM_PROP
 }
}

Fensterfunktion

In der Fensterfunktion wird das Ereignis WM_CONTEXTMENU gefangen


  case WM_CONTEXTMENU:
     POINT pt;

     pt.x = LOWORD(lParam);
     pt.y = HIWORD(lParam);
     ScreenToClient(hWnd, &pt);

     if (popupContext(hWnd, POPUPMENU, pt))  {

Im Gegensatz zu WM_LBUTTONDBLCLK kommen hier die Koordinaten der Maus in Screen- und nicht in Client-koordinaten. Für unsere Zwecke wird sie mit der Windowsfunktion ScreenToClient umgewandelt.


BOOL popupContext(HWND hwnd, int ResID, POINT pt)
{
RECT rc;

   GetClientRect(hwnd, &rc);
   if (PtInRect(&rc, pt)) {
        ClientToScreen(hwnd, &pt);
        popupDisplay(hwnd, ResID, pt);
        return TRUE;
    }
    return FALSE; // no menu is displayed.
}

Nachdem geprüft ist, ob der Cursor auch im Clientbereich des Fensters ist, kann nun das Menü erzeugt und bearbeitet werden.


static void popupDisplay(HWND hwnd, int ResID, POINT pt)
{
HMENU hmenu; // top-level menu
HMENU hmenuTrackPopup; // pop-up menu

hmenu = LoadMenu(GlobalInstance, MAKEINTRESOURCE(ResID));
if (hmenu == NULL) return;

hmenuTrackPopup = GetSubMenu(hmenu, 0);
TrackPopupMenu(hmenuTrackPopup, TPM_RIGHTBUTTON |
TPM_LEFTBUTTON,
pt.x, pt.y, 0, hwnd, NULL);
DestroyMenu(hmenu);
}

Mit LoadMenu wird das in der Ressource definierte Menü geladen. TrackPopupMenu kann nicht das Toplevel-Menü anzeigen. Dieses wird nur als Dummy benötigt. Stattdessen wird mit GetSubMenu der erste Menüpunkt für die weitere Bearbeitung ermittelt. Nach der Verfolgung der Maus wird das Menü wieder zerstört.

In der weiteren Bearbeitung wird durch das Auswählen eines Punktes ein WM_COMMAND ausgelöst.

Literatur

Nancy Cluts: Programmierung für die Benutzeroberfläche von Windows 95, Microsoft Press, 1995. Seite 247-248.

Windows Programmierung: Dialogboxen

Eine einfache Dialogbox

Die Dialogbox wird im Ressource-Editor gestaltet. Das Ergebnis ist eine Datei mit der Endung rc, die zum Projekt hinzugebunden wird. Der Ressource-Compiler erstellt eine Datei mit der Endung res.

Im Ressource-Editor erhält sowohl die Dialogbox als auch jedes Kontrollelement des Dialogs eine Kennung (SHORT), über die sie identifizierbar ist. Wie bei einem Fenster gibt es eine Fensterfunktion, die das Verhalten des Fensters festlegt. Eine Dialogfensterfunktion unterscheidet sich in einigen Details von der normalen Fensterfunktion. So lautet die Initialisierungsnachricht WM_INITDIALOG statt WM_CREATE. Das Ende der Dialogbox erfolgt durch den Aufruf von EndDialog. Dabei wird der zweite Parameter der von EndDialog der aufrufenden Funktion zurückgegeben.


// Modul fuer die Erzeugung einer Dialogbox ABOUT
// (C) Arnold Willemer

#include 

#include "main.h"    // hier steht die Deklaration von GlobalInstance
#include "fam_rc.h"  // Ressourcen-Header

LONG APIENTRY InfoDlgFkt(HWND hWnd, UINT 
message, UINT wParam, LONG lParam) { switch (message) { case WM_INITDIALOG: // Initialisierung der Elemente break; case WM_COMMAND: switch(wParam) { case IDOK: EndDialog(hWnd, IDOK); return TRUE; case IDCANCEL: EndDialog(hWnd, IDCANCEL); return TRUE; } // switch COMMAND } return FALSE; } About(HWND hWnd) { if (DialogBox(GlobalInstance, MAKEINTRESOURCE(DIA_INFO), (HWND)hWnd, (DLGPROC)InfoDlgFkt)==IDOK) { return 1; } else { return 0; } }

Der Start der Dialogbox erfolgt durch den Aufruf der Funktion DialogBox oder einer ihrer Verwandten. Durch die Parameter wird festgelegt, welche Ressource-ID und welche Dialogfensterfunktion herangezogen wird. Das Programm erhält erst dann wieder die Kontrolle, wenn die Dialogfensterfunktion EndDialog aufruft und mit dieser Funktion auch den Rückgabewert von DialogBox bestimmt.


Dialogbox mit Parametern

In vielen Fällen möchte man der Dialogbox Daten übergeben. Dies funktioniert natürlich auch mit globalen Variablen. Abgesehen von der mangelnden Eleganz scheitert diese Methode spätestens bei rekursiven Aufrufen der Dialogbox.

Die Funktion DialogBoxParam verhält sich wie DialogBox. Sie erlaubt aber einen LONG als Parameter, der an die Dialogbox weitergereicht wird. Da man darüber auch einen Zeiger übergeben kann, sind beliebige Daten zu übermitteln.


   if (DialogBoxParam(GlobalInstance,
                      MAKEINTRESOURCE(DIA_FELD), (HWND)hWnd,
                      (DLGPROC)FeldDlgFkt,
                      (LPARAM)ZeilenIndex)==IDOK) {

In der Fensterfunktion wird er beim Eintreten der Message WM_INITDIALOG einfach aus dem lParam gelesen. Hierin befindet sich der übergebene Wert.


LONG APIENTRY FeldDlgFkt(HWND hWnd, UINT message, 
UINT wParam, LONG lParam) { static long ZeilenIndex; switch (message) { case WM_INITDIALOG: ZeilenIndex = (long) lParam;

Da bei jedem Ereignis die Funktion erneut gestartet wird, muss sich die Dialogbox die übermittelten Daten merken. Da man globale Variablen vermeiden will, muss man mit lokalen Fensterdaten arbeiten.


Dialogbox mit lokalen Fensterdaten


{
tData *myData;

   myData = (tData *) GetWindowLong(hWnd, GWL_USERDATA);
   switch (message) {
   case WM_INITDIALOG:
      myData = (tData *)lParam;
      SetWindowLong(hWnd, GWL_USERDATA, (LONG)myData);
      ...

Hier werden Daten im WM_INITDIALOG als Parameter übernommen (siehe oben) und gleich in die lokalen Fensterdaten geschoben (SetWindowLong). Bei jedem Ereignis werden diese lokalen Daten wieder neu geladen (GetWindowLong). Dadurch können mehrere Fenster gleichzeitig existieren, die mit eigenen, unabhängigen Daten arbeiten.

Nicht-modale Dialogboxen

Eine normale Dialogbox wird durch den Aufruf von DialogBox gestartet. Das Programm bleibt an dieser Stelle stehen und wartet auf das Schließen der Dialogbox. Der Anwender kann nicht weiterarbeiten, bis der Dialog beendet ist. Dieses Verhalten nennt man modal.

In bestimmten Situationen ist dieses Verhalten unerwünscht. Ein Beispiel ist ein Fortschrittsanzeiger, der nur Sinn macht, wenn im Hintergrund das Programm weiterläuft. Das gleiche gilt für alle Abbruch-Dialoge.

Eine nicht-modale Dialogbox wird nicht mit DialogBox, sondern durch den Aufruf CreateDialog und ein anschließendes ShowWindow erzeugt. Die Parameter für CreateDialog entsprechen denen von DialogBox. Auch die Fensterfunktion ist weitgehend identisch. Lediglich das Ende der Dialogbox wird durch DestroyWindow statt mit EndDialog eingeleitet. Das Programm läuft nach der Erzeugung ungehindert weiter.

Im Beispiel wird eine Dialogbox erzeugt, die Fehlermeldungen in einer Liste darstellen soll.


HWND hWndDlg = 0;

LRESULT CALLBACK DlgFkt_ErrorList(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
DestroyWindow(hDlg);
hWndDlg = 0;
return TRUE;
}
}
return FALSE;
}

HWND DiaErrorList(HWND hWnd)
{
hWndDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_ERRORLIST), hWnd, (DLGPROC)DlgFkt_ErrorList);
ShowWindow(hWndDlg, SW_SHOW);
return hWndDlg;
}

void Print2List(char *str)
{
if (hWndDlg) {
SendMessage(GetDlgItem(hWndDlg, IDC_LIST), LB_ADDSTRING, 0, (LPARAM)str);
} else {
Print2File(str);
}
}

Windows Programmierung: Property Sheet

Das Property Sheet ist kein Kontrollelement, sondern ein kompletter Dialog. Man kann ihn also nicht einfach in einem normalen Fenster unterbringen. Ein Property Sheet besteht aus mehreren Seiten, die jeweils wie eine Dialogbox in den Ressourcen definiert wird. Es werden lediglich der Ok- und der Abbruch-Buttonweggelassen, da diese vom Property-Sheet geliefert werden.

Erzeugung eines Property Sheets

Zunächst wird im Ressource Editor für jede Seite ein Dialog erzeugt. Auch eine Dialogfensterfunktion wird für jede der Seiten geschrieben. Für jede Seite wird eine Struktur PROPSHEETPAGE zur Beschreibung angelegt, die in einem Array organisiert sind. Die Adresse dieses Arrays wird in der Struktur PROPSHEETHEADER eingetragen und mit der Adresse dieser Variable als Parameter wird die Funktion PropertySheet aufgerufen.

Zur Vereinfachung des Beispiels wird nur ein Dialog und eine Dialogfensterfunktion für mehrere Seiten verwendet. Die Ressource-ID ist IDD_PERSON und die Fensterfunktion des Dialoges ist DataInput.


#include <COMMCTRL.H>

extern HINSTANCE hInst;

HWND CreatePropertySheet (HWND hwndOwner)
{
PROPSHEETPAGE psp [2];
PROPSHEETHEADER psh = {0};

	psp[0].dwSize = sizeof (PROPSHEETPAGE);
	psp[0].dwFlags = PSP_USETITLE;
	psp[0].hInstance = hInst;
	psp[0].pszTemplate = MAKEINTRESOURCE (IDD_PERSON);
	psp[0].pszIcon = NULL;
	psp[0].pfnDlgProc = DataInput;
	psp[0].pszTitle = "Person";
	psp[0].lParam = 0;

	psp[1].dwSize = sizeof (PROPSHEETPAGE);
	psp[1].dwFlags = PSP_USETITLE;
	psp[1].hInstance = hInst;
	psp[1].pszTemplate = MAKEINTRESOURCE (IDD_PERSON);
	psp[1].pszIcon = NULL;
	psp[1].pfnDlgProc = DataInput;
	psp[1].pszTitle = "Vater";
	psp[1].lParam = 0;

	psh.dwSize = PROPSHEETHEADER_V1_SIZE;
	psh.dwFlags = PSH_PROPSHEETPAGE;
	psh.hwndParent = hwndOwner;
	psh.hInstance = hInst;
	psh.pszIcon = NULL;
	psh.pszCaption = (LPSTR)"Family";
	psh.nPages = sizeof (psp) / sizeof (PROPSHEETPAGE);
	psh.ppsp = (LPCPROPSHEETPAGE) &psp;

	return (HWND)PropertySheet (&psh);
);

Seitendefinition

Jede Seite erhält die Information über den zugehörigen Dialog. Das ist die Ressource-ID in pszTemplate und die Dialogfensterfunktion in pfnDlgProc. Das Element dwFlags besagt, welche Elemente vom Anwender vom Standard abweichend behandelt werden. In diesem Fall ist das pszTitle mit dem Text, der in der zugehörigen Lasche erscheinen soll.

Headerdefinition

Hier werden die Dialogseiten zusammengefasst. Die Adresse des Seiten-Arrays wird in ppsp angegeben und die Anzahl der Seiten in nPages hinterlegt. Der Textzeiger in pszCaption legt fest, welcher Text in der Titelleiste des Dialogs erscheint.

Wird im dwFlags-Element PSH_NOAPPLYNOW hinzugenommen (durch Odern mit | ), erscheint der Apply-Button nicht.

Bei der Bestimmung der Grösse der PROPSHEETHEADER-Struktur ist die Angabe ungewöhnlich. Hier verwendet man üblicherweise eine Konstruktion wie

  psh.dwSize = sizeof(PROPSHEETHEADER);
Allerdings besteht das Problem, daß mit der Version 4.71 der COMCTL32.DLL die Struktur etwas größer geworden ist. Da sizeof eine Konstante zur Laufzeit liefert, würde ein Entwicklungssystem, das einen Update hat, ein Programm erzeugen, das unter alten Versionen nicht läuft. Man kann dies verhindern, indem man
  psh.dwSize = PROPSHEETHEADER_V1_SIZE;
verwendet. Will man dynamisch arbeiten, muß die Funktion DllGetVersion verwendet werden, um zu ermitteln, welche Version auf dem aktuellen System läuft.

Rückgabewert

Bei modalen Dialogen wird ein positiver Wert zurückgegeben, wenn der Dialog mit OK verlassen wurde, ansonsten -1.

Fensterfunktion

Die Fensterfunktion unterscheidet sich auf den ersten Blick nicht von der eines normalen Dialogs. Sie verwaltet eine Seite, nicht den gesamten PropertySheet.


BOOL APIENTRY DataInput(HWND hDlg, UINT message, UINT wParam, LONG lParam)
{
PROPSHEETPAGE *ps;

switch (message) {
case WM_INITDIALOG:
// Save the PROPSHEETPAGE information.
ps = (PROPSHEETPAGE *)lParam;
// sichere das in den Fensterdaten...
return TRUE;

case WM_NOTIFY:
switch (((NMHDR FAR *)lParam)->code) {
case PSN_SETACTIVE:
break;

case PSN_APPLY:
if (AllOk()) {
SetWindowLong(hDlg, DWL_MSGRESULT,
PSNRET_NOERROR);
} else {
SetWindowLong(hDlg, DWL_MSGRESULT,
PSNRET_INVALID_NOCHANGEPAGE);
}
return TRUE;

case PSN_KILLACTIVE:
SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
return 1;

case PSN_RESET:
SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
break;
}
}
return FALSE;
}

Bei der Initialisierung wird der Zeiger auf die PropertyPage, also die Seite übergeben. Da dieser Dialog mehrfach verwendet wird, muss man die Daten für jedes Objekt in den Fensterdaten sichern. Dies wird an anderer Stelle beschrieben. Wird für jede Seite eine andere Dialogfensterfunktion verwendet, ist das nicht notwendig.

Die Fensterfunktion darf nicht EndDialog aufrufen. Statt der Behandlung von OK und Abbruch werden die WM_NOTIFY-Ereignisse PSN_SETACTIVE, PSN_APPLY, PSN_KILLACTIVE und PSN_RESET bearbeitet

PSN_APPLY

Das Ereignis zeigt an, dass die Änderungen angewandt werden sollen. Es kann also OK, Apply oder der Schliess-Button angewählt worden sein. lParam enthält den Zeiger auf eine PSHNOTIFY-Struktur. In dessen lParam-Feld steht TRUE, wenn der Anwender OK gedrückt hat.

Wird PSNRET_INVALID_NOCHANGEPAGE zurückgegeben, wird verhindert, dass die Daten übernommen werden und der Fokus wird auf diese Seite gebracht. Die Rückgabe von PSNRET_NOERROR akzeptiert die Änderungen.

Die Rückgabe erfolgt in diesem Fall dadurch, dass SetWindowLong mit dem Parameter DWL_MSGRESULT gerufen wird. Der dritte Parameter sind die oben genannten PSNRET-Konstanten. Damit dies gelesen wird, muss TRUE returniert werden.

PSN_KILLACTIVE

Diese Seite verliert den aktiven Zustand. Sei es, weil eine andere Seite angewählt wird oder weil OK gewählt wurde.

Auf die gleiche Weise wie bei PSN_APPLY kann ein Wechseln der Seite verhindert werden. Es sollte eine Messagebox erscheinen, die dieses Verhalten dem Anwender erläutert.

PSN_RESET

Der PropertySheet wird zerstört. Alle Änderungen seit dem letzten PSN_APPLY sind zu verwerfen.

PSN_SETACTIVE

Diese Seite wird aktiviert. Hier erfolgen die Initialisierungen.

Apply-Button

Der Apply-Button ist zu Anfang disabled. Um ihn zu aktivieren, muss die Anwendung die Nachricht PSM_CHANGED an den Property Sheet senden. Durch Senden von PSM_UNCHANGED wird er wieder deaktiviert. Sollte der durch den Apply-Button gesetzte Zustand unumkehrbar sein, sollte die Seite PSM_CANCELTOCLOSE senden. Dadurch wird der Text des Ok-Buttons in Schliessen geändert.

Wechseln der Seiten

Durch Senden der Nachrichten PSM_ADDPAGE und PSM_REMOVEPAGE kann man dynamisch Seiten hinzufügen und löschen.

Hinzufügen

PSM_ADDPAGE 
    wParam = 0; 
    lParam = (LPARAM) (HPROPSHEETPAGE) hpage;

oder


BOOL PropSheet_AddPage(HWND hPropSheetDlg, HPROPSHEETPAGE hpage);
hpage
Handle der Seite, die hinzugefügt werden soll. Die Seite muß zuvor mit der Funktion CreatePropertySheetPage erzeugt worden sein.
HPROPSHEETPAGE
CreatePropertySheetPage(LPCPROPSHEETPAGE lppsp);
Als Parameter erwartet CreatePropertySheetPage die Adresse der PROPSHEETPAGE-Struktur, die die Seite definiert. Der Rückgabewert ist das gesuchte Handle.

Löschen

PSM_REMOVEPAGE 
    wParam = (WPARAM) (int) index; 
    lParam = (LPARAM) (HPROPSHEETPAGE) 
hpage); oder


VOID PropSheet_RemovePage(HWND hPropSheetDlg, int index, HPROPSHEETPAGE hpage);
index
0-basierter Index der zu entfernenden Seite
hpage
Handle der zu entfernenden Seite.
Beim Löschen kann eines von beiden angegeben werden. Wird beides spezifiziert, geht hpage vor.

Windows Programmierung: Dateiauswahlbox

Die Dialogbox zur Auswahl von Dateien ist eine Errungenschaft von Windows 3.1. Bei vorigen Versionen war sie noch nicht verfügbar. Damit aber auch Windows 3.0-Anwender die Auswahlbox benutzen können, hat Microsoft es gestattet, die für die Standarddialoge zuständige Datei COMMDLG.DLL frei zu kopieren.

Die Prototypen und Datentypen für diese Dialoge finden sich in der Datei COMMDLG.H und müssen im Programm eingebunden werden.

#include <COMMDLG.H>

Ein Beipiel

Typischerweise wird die Dateiauswahlbox aufgerufen, wenn Datei-Öffnen vom Anwender aufgerufen wurde. Damit muß der Aufruf in die Fensterfunktion an die Stelle, wo die Menüauswertung geschieht. Vorher wird der Importfilter gesetzt und eine Variable der Datenstruktur OPENFILENAME definiert. Diese Variable wird nullgesetzt und sollte nicht jedesmal neu erzeugt werden, da dann das Programm weiß, wo das letzte Mal geladen wurde und dort wieder aufsetzen wird. Wie bei vielen Windows-Strukturen muß auch hier das Feld lStructSize mit der Größe der Struktur vorbesetzt sein.


#define IMPORTFILTER "Diary (*.dry)\0*.dry\0Address (*.adr)\0*.adr\0All Files(*.*)\0*.*\0\0"
...
static OPENFILENAME DateiSel = {0}; // hier wird die Struktur geloescht!
...
case WM_COMMAND: // message: command from application menu
switch( LOWORD( wParam )) {
case M_OPEN:
char FileName[_MAX_PATH+1] = "";
// Die Fileselector-Struktur vorbesetzen
DateiSel.lStructSize = sizeof (OPENFILENAME) ;
DateiSel.hwndOwner = hWnd;
DateiSel.nMaxFile = _MAX_PATH;
DateiSel.lpstrFile = FileName;
DateiSel.lpstrFilter = IMPORTFILTER;
if (GetOpenFileName(&DateiSel)) {
switch (DateiSel.nFilterIndex) {
case 1: //
MessageBox(hWnd, "Diary", "hallo", MB_OK);
break;
case 2: //
MessageBox(hWnd, "Address", "hallo", MB_OK);
break;
}
}
break;

Im String FileName wird nach dem Aufruf der Dialogbox der ausgewählte Dateiname stehen, da diese Adresse in lpstrFile festgelegt wurde.

Die Funktionen

Die Funktion GetOpenFileName eröffnet eine Dialogbox zur Auswahl einer Datei, die gelesen werden soll. Der Parameter enthält eine Struktur, in der die Dialogbox weitgehend konfiguriert wird. Das wichtigste Element dieser Strukur ist der Zeiger lpstrFile, der den Speicherbereich angibt, in dem anschließend die ausgewählte Datei benannt wird.

Die Funktion GetSaveFileName dient der Selektion einer Datei zum Sichern.

Die Funktion GetFileTitle isoliert aus der bezeichneten Datei den reinen Dateinamen ohne Laufwerkskennung und Pfadnamen.

Die Prototypen der Funktionen GetOpenFileName, GetSaveFileName und GetFileTitle:


BOOL    WINAPI GetOpenFileName(OPENFILENAME FAR*);
BOOL    WINAPI GetSaveFileName(OPENFILENAME FAR*);
int     WINAPI GetFileTitle(LPCSTR, LPSTR, UINT);

Die Struktur OPENFILENAME

Wie man an den Parametern der Funktionen sieht, spielt die Struktur OPENFILENAME eine beson- dere Rolle. Sie dient als Struktur, um die Parameter der Funktion aufzunehmen.


typedef struct tagOFN
{
    DWORD   lStructSize;
    HWND    hwndOwner;
    HINSTANCE hInstance;
    LPCSTR  lpstrFilter;
    LPSTR   lpstrCustomFilter;
    DWORD   nMaxCustFilter;
    DWORD   nFilterIndex;
    LPSTR   lpstrFile;
    DWORD   nMaxFile;
    LPSTR   lpstrFileTitle;
    DWORD   nMaxFileTitle;
    LPCSTR  lpstrInitialDir;
    LPCSTR  lpstrTitle;
    DWORD   Flags;
    UINT    nFileOffset;
    UINT    nFileExtension;
    LPCSTR  lpstrDefExt;
    LPARAM  lCustData;
    UINT    (CALLBACK *lpfnHook)(HWND, UINT, 
WPARAM, LPARAM); LPCSTR lpTemplateName; } OPENFILENAME; typedef OPENFILENAME FAR* LPOPENFILENAME;

Zunächst wird in lStructSize die Größe der OPENFILENAME-Struktur erwartet. Diese wird mit sizeof(OPENFILENAME) festgelegt.

Der Dateiname

Die wichtigste Aufgabe der Dialogbox ist die Lieferung des Dateinamens.


static OPENFILENAME DateiSel = {0};

  DateiSel.nMaxFile    = _MAX_PATH;
  DateiSel.lpstrFile   = FileName;
  DateiSel.lpstrDefExt = "txt";

lpstrFile zeigt auf eine Stringvariable. Die Variable mit ihrem Speicherraum muß vom Programmierer definiert werden. Wichtig: Der String muß bei Start der Dialogbox vorbesetzt sein, typischerweise mit einem Leerstring. Ansonsten kann es sein, daß die Dialogbox nicht startet. Die Läge dieses Strings wird im Element nMaxFile abgelegt.

Nach Ende der Dialogbox befindet sich hier der ausgewählte Dateiname inklusive Laufwerksbuchstabe und Pfad.

Beim Sichern ohne eine Angabe der Extension wird in den meisten Fällen erwartet, dass das Programm seine eigene Extension anhängt. Dies wird durch Vorbesetzen des Elements lpstrDefExt erreicht.

Nicht ohne Filter

LPCSTR lpstrFilter
Hier wird eine Liste der Dateimasken übergeben, die bei Benutzung der Dialogbox durch eine Klapplistbox ausgewählt werden kann. Dabei werden die Strings direkt hintereinander geschrieben. Anders ausgedrückt, werden die Teilstrings durch eine binäre Null getrennt. Das Ende des gesammten Filters wird durch das Auftreten zweier Nullen hintereinander kenntlich.

Es bilden immer zwei Strings ein Paar. Das erste ist die Bezeichnung im Klartext, das zweite ist die Maske. Werden mehrere Masken für eine Auswahl benötigt, kann man sie einfach hintereinander schreiben. Wie üblich wird jeder String durch ein Nullbyte beendet. Das Ende des Filters wird durch zwei Nullen kenntlich gemacht. Hier zwei Beispielstrings:


"Stammbaum (*.dbf)\0*.dbf\0GEDCOM (*.ged)\0*.ged\0\0"
"Diary (*.dry)\0*.dry\0Address (*.adr)\0*.adr\0All Files(*.*)\0*.*\0\0"
Die Maske in Klammern dient nur der Anzeige und hat keine inhaltliche Bedeutung.

DWORD nFilterIndex
Nach Bearbeiten der Dialogbox liefert nFilterIndex welcher Filter vom Anwender ausgewählt wurde. Damit kann beispielsweise bei Importen festgestellt werden, welcher Funktion aufgerufen werden muß, um die Datei richtig zu lesen. Beim Schreiben der Datei ist es noch wichtiger, da so bestimmt werden kann, in welchem Format oder welche Inhalte geschrieben werden sollen. Der Index beginnt übrigens bei 1!

Flags

DWORD Flags
Hier werden spezielle Optionen gesetzt, die die Dialogbox konfigurieren. Die folgenden Wer- te können dabei durch Addition kombiniert werden:
OFN_READONLY
bewirkt, daß der Schreibschutzknopf bei Start der Dialogbox angewählt ist.
OFN_OVERWRITEPROMPT
bewirkt in einer "Sichern als..."-Box, daß bei Existenz der Datei nachgefragt wird, ob sie öberschrieben werden darf.
OFN_HIDEREADONLY
macht den Schreibschutzknopf unsichtbar #define OFN_NOCHANGEDIR 0x00000008 zwingt die Dialogbox dazu, das aktuelle Verzeichnis dahin zuröckzusetzen, wo es war, als die Dialogbox erzeugt wurde.
OFN_SHOWHELP
erzeugt einen Hilfsknopf. Dabei darf hwndOwner nicht NULL sein.
OFN_NOVALIDATE
erlaubt auch ungültige Zeichen im Dateinamen.
OFN_ALLOWMULTISELECT
erlaubt die Auswahl mehrerer Dateien. Im String för den Dateinamen steht dann zu- nächst Laufwerksbezeichnung und Pfadname, dann jeweils durch Leerzeichen abge- trennt die Liste der angewählten Dateinamen.
OFN_PATHMUSTEXIST
erzwingt, daß der Anwender nur existierende Pfadnamen angeben kann.
OFN_FILEMUSTEXIST
erzwingt, daß der Anwender nur existierende Dateinamen angeben kann.
OFN_CREATEPROMPT
meldet, wenn die angegebene Datei nicht existiert und ermöglicht es dem Benutzer, ihre Erzeugung in einer Dialogbox zu veranlassen.
OFN_NOREADONLYRETURN
erzwingt, daß die Datei weder schreibgeschützt noch ein schreibgeschütztes Verzeichnis ist.

Windows Programmierung: Ordnerauswahl

Im Gegensatz zum Selektieren von Dateien hat das Selektieren von Ordnern einen anderen typischen Dialog. Auch die Programmierung erfolgt anders. Der Zugang erfolgt per COM auf die Shell-Schnittstelle von Windows. Daraus leiten sich zwei Notwendigkeiten ab. Zunächst muss shlobj.h eingebunden werden. Zum anderen muss COM einmal initialisiert werden. Das kann beispielsweise bei WM_CREATE der Fensterfunktion passieren.


#include <shlobj.h>

	WM_CREATE:
		...
		CoInitialize(0);

Die zentrale Funktion heisst SHBrowseForFolder und arbeitet mit einer Struktur BROWSEINFO.

WINSHELLAPI LPITEMIDLIST WINAPI 
     SHBrowseForFolder(LPBROWSEINFO lpbi);
Die folgende Routine startet einen Selektionsdialog für Verzeichnisse.


char strOrdner[_MAX_PATH] = "";
BROWSEINFO browseInfo = {0};
browseInfo.pszDisplayName = strOrdner; // wir verwenden diesen nicht
browseInfo.hwndOwner = hWnd;
LPITEMIDLIST ret;
ret = SHBrowseForFolder(&browseInfo);
if (ret) {
	// OK selektiert
	if (SHGetPathFromIDList(ret, strOrdner)) {
		MessageBox(hWnd, strOrdner, "selektiert", MB_OK);
	}
}

Das Feld pszDisplayName muss auf einen Speicherplatz zeigen, in den die Dialogbox den "Anzeigenamen" ablegt. Dieser besteht aber aus dem nackten Verzeichnisnamen ohne Pfad. Das nutzt dem Programmierer meist wenig. Um den kompletten Pfadnamen zu erlangen, wird der Rückgabezeiger von SHBrowseForFolder an die Funktion SHGetPathFromIDList weitergegeben, der im Speicherbereich des zweiten Arguments den kompletten Pfad ablegt. Da der Displayname nicht gebraucht wird, wird hier der Einfachheit halber einfach der gleiche Speicher wiederverwendet.

Variationen

Das Flag ulFlags der BROWSEINFO kann mit Konstanten besetzt werden, die die Auswahl einschränken. Das wird durch Grauschalten des OK-Buttons erreicht. Eine Kombination durch Odern ist möglich.

BIF_BROWSEFORCOMPUTER nur Computer
BIF_BROWSEFORPRINTER nur Drucker
BIF_RETURNONLYFSDIRS nur Verzeichnisse

Programmierung von Kontrollelementen

Willemers Informatik-Ecke

Die Kontrollelemente besitzen einige Gemeinsamkeiten. Es aus Sicht von Windows lediglich spezialisierte Fenster und gehorchen gleichen Gesetzen.

Übersicht

Element Fensterklasse Stil-Präfix
Staticfeld "STATIC" SS_
Schaltfläche (Button) "BUTTON" BS_
Eingabefeld "EDIT" ES_
Listbox "LISTBOX" LBS_
Combobox "COMBOBOX" CBS_
Scrollbar "SCROLLBAR" SBS_
Baumdiagramm WC_TREEVIEW TVS_
ListView WC_LISTVIEW LVS_

Erzeugung eines Kontrollelements

Kontrollelemente werden oft in Dialogboxen erstellt und werden durch den Dialogstart miterzeugt. Werden diese Elemente aber in Fenstern benötigt werden sie mit CreateWindow erzeugt. Die Parameter von CreateWindow sind:
LPCTSTR: Fensterklasse
Hier wird die vordefinierte Fensterklasse angegeben, die wie bei eigenen Fenstern durch einen String festgelegt ist. Beispiel: "LISTBOX"
LPCTSTR: Fenstertext
Bei STATIC, EDIT und BUTTON ist dies der Text, der im Element zu sehen ist. Bei Standardfenstern steht er im Balken.
DWORD: Fensterstil
Eine Veroderung von WS_-Konstanten. Typischerweise werden die Konstanten WS_VISIBLE|WS_CHILD|WS_BORDER als Ausgangspunkt benötigt. Dazu kommen noch elementspezifische Stile, wie etwa bei Listboxen LBS_SORT.
int, int, int, int: x, y, Breite, Hoehe
Position im Elternfenster
HWND: das Elternfenster
die Fensterfunktion des Elternfensters erhät die Nachrichten des Kontrollelements
HMENU: Ressource-ID
Jedes Element hat eine solche ID, die in ihrem Umfeld eindeutig sein muß Bei Standardfenstern wird an dieser Stelle die Menü-ID angegeben. Normale IDs müssen hier gecastet werden: (HMENU)ID_MYCONTROL.
HANDLE: Instanz
Die Instanz der Anwendung, wie bei WinMain übergeben.
LPVOID: Parameter
Dieser Wert ist bei Kontrollelementen 0. Er wird bei der Erzeugung von Fenstern und Dialogen gebraucht.

Ein Beispiel zur Erzeugung eines Eingabefeldes:


CreateWindow("EDIT", "",
WS_VISIBLE|WS_CHILD|WS_BORDER,
10, 50, 300, 30, // Positionen auf dem Elternfenster
hWnd, (HMENU)ID_MYCONTROL, hInst, 0);

Bei den Common Controls sollte noch der Aufruf von InitCommonControls() erfolgen. In der neueren Variante und lautet z. B. für Tree Views:


INITCOMMONCONTROLSEX icc;
  icc.dwSize = sizeof(icc);
  icc.dwICC = ICC_TREEVIEW_CLASSES;
  InitCommonControlsEx(&icc);

Ein Include von "commctrl.h" ist obligatisch.

Ereignisse bearbeiten

Die Ereignisse gelangen in die Fensterfunktion des Elternfensters. Dies kann ein Dialog oder ein gewöhnliches Fenster sein. Die Kontrollelemente lösen ein WM_COMMAND aus.

LOWORD(wParam) enthält die Ressource-ID
HIWORD(wParam) beschreibt das Ereignis


  case WM_COMMAND:
    switch(LOWORD(wParam))
{ // welches Kontrollelement loest die Nachricht aus? case ID_MYCONTROL: switch (HIWORD(wParam))
{ // welches Ereignis ist genau eingetreten? case LBN_DBLCLK: // ein Doppelklick auf eine Listbox break; case LBN_SELCHANGE:

Einige der neueren Kontrollelemente verwenden nicht die Nachricht WM_COMMAND, sondern WM_NOTIFY. Bei diesem Ereignis wird im lParam ein Pointer auf eine Struktur NMHDR übergeben, die in den ersten Elementen bei allen Kontrolls gleich sind. Sie enthalten:

HWND hwndFrom Das HWND des Kontrollelements
UINT idFrom Die Ressource-ID des Kontrollelements
UINT code Der Code des Ereignisses

Eine typische Bearbeitung der Ereignisse sieht also so aus:


LPNMHDR hdr;

    case WM_NOTIFY:
        hdr = (LPNMHDR)lParam;
        switch(hdr->code) {
           // unterscheide den Notify-Code

Manipulation

Ein Kontroll kann Befehle erhalten oder man kann Informationen erfragen. Der Kontakt des Programmes mit seinen Kontrollelementen geschieht durch das Senden von Nachrichten.

Im Beispiel werden die Daten, die hinter dem ausgewählten Listboxelement abgelegt sind, angefragt. Dies benötigt zwei Schritte. Erst muß ermittelt werden, welches Element selektiert ist und anschließend erfragt man die Daten. Beide Vorgänge werden durch die Funktion SendMessage ausgelöst. Im ersten Parameter wird das HWND des Kontrollelements benoetigt, im Beispiel hListe. Der zweite Parameter enthält das Kommando. Das ist LB_CURSEL zum Ermitteln des aktuell selektierten Element und LB_GETITEMDATA zur Frage nach den Daten. Die folgenden beiden Parameter entsprechen dem wParam und lParam aus der Fensterfunktion.


	ListIndex = SendMessage(hListe, LB_GETCURSEL, 0, 0);
	Person = (tPerson *)SendMessage(hListe, LB_GETITEMDATA, 
ListIndex, 0);

Windows Programmierung: Buttons

Einführende Informationen über die Programmierung von Kontrollelementen werden hier vorausgesetzt.

Stile

Der Stil gibt an, welcher Art der Button ist. Es gibt drei grundsätzliche Ausprägungen: Pushbutton, Checkbox und Radiobutton.

Pushbutton

Dies ist der gewöhnliche Button, der gedrückt wird und eine Aktion auslöst, indem er eine WM_COMMAND-Nachricht an das Elternfenster sendet. In der Variante Default-Pushbutton, ist er etwas stärker umrandet und reagiert, wenn der Anwender die Return-Taste drückt.
BS_PUSHBUTTON
BS_DEFPUSHBUTTON

Checkbox

Ein kleines Rechteck, in dem man ein Kreuz erscheinen und verschwinden lassen kann. Ein Text befindet sich rechts davon (oder links, wenn man BS_LEFTTEXT angibt). Das 3-State Element kennt noch einen dritten Zustand: gedimmt zeigt an, daß der Button nicht anwählbar ist. Die AUTO-Variante schaltet selbstständig das Kreuz ein und aus.
BS_CHECKBOX
BS_3STATE
BS_AUTOCHECKBOX
BS_AUTO3STATE

Radiobutton

Ein Radiobutton verhält sich einzeln gleich einer Checkbox. Lediglich im Aussehen ist ein Unterschied sichtbar, der Radiobutton ist rund und hat einen Punkt in der Mitte, wenn er ausgewählt ist.

In Gruppen zusammengefaßt, kann aber nur maximal einer von Ihnen angewählt werden. In der AUTO-Variante braucht dies nicht einmal der Programmierer selbst zu realisieren. Zu einer Gruppe gehören diejenigen, die das gleiche Elternfenster besitzen. Dies wird üblicherweise durch den Einsatz eines Rahmens erreicht.

BS_RADIOBUTTON
BS_AUTORADIOBUTTON

Groupbox

Dies ist ein solcher Rahmen, um Radiobuttons zu gruppieren. In der linken oberen Ecke kann ein Text stehen, um eine gemeinsame Überschrift zu haben.

BS_GROUPBOX

Weitere Attribute

BS_OWNERDRAW
Der Programmierer übernimmt die Darstellung des Buttons
BS_LEFTTEXT
Bei Radiobuttons und Checkboxes erscheint der Text links vom Feld.

Ereignis Button angeklickt

Während andere Kontrollelemente noch prüfen müssen, welches Ereignis eingetroffen sein könnte, ist dies bei Buttons im Allgemeinen ein Klick. Dadurch ergibt sich eine vereinfachte Ereignisabfrage:


   case WM_COMMAND:
      switch(LOWORD(wParam)) {
         case ID_BUTTON:
            break;
      }
      break;

Buttons mit Haken

Prüfen, ob ein Button mit einem Haken versehen ist. Funktioniert bei Radiobuttons und Checkboxes


HWND hButton;
   hButton = GetDlgItem(hWnd, ID_BUTTON);
   if (SendMessage(hButton , BM_GETCHECK, 0, 0)==BST_CHECKED) {

Setzen eines Hakens

  SendMessage(hButton, BM_SETCHECK, BST_CHECKED, 0);

Da BST_CHECKED als 1 definiert ist und BST_UNCHECKED als 0, kann man auch den booleschen Ausdruck als Parameter verwenden.

Windows Programmierung: Das Editfeld

Stile

Der Stil des Elementes wird bei der Erzeugung festgelegt. Neben den allgemeinen Fensterstilen besitzt das Editfeld eigene Stile:
ES_AUTOHSCROLL
Der Text wird 10 Zeichen weitergeschoben, wenn der Rand des Elements erreicht wird.
ES_LEFT
linksbündig
ES_CENTER
zentriert
ES_RIGHT
rechtsbündig
ES_LOWERCASE
wandelt alle Eingabezeichen in Kleinbuchstaben
ES_UPPERCASE
wandelt alle Eingabezeichen in Großbuchstaben
ES_NOHIDESEL
Bei Verlust des Eingabefokus wird normalerweise die Selektion verborgen. Dieser Stil verhindert dies.
ES_OEMCONVERT
Die Eingaben werden von ANSI in den OEM-Zeichensatz konvertiert.
ES_PASSWORD
Für jedes eingegebene Zeichen wird ein Stern angezeigt.
ES_READONLY
Änderungen am Inhalt des Eingabefeldes werden verhindert.
ES_MULTILINE
Mehrzeiliges Eingabefeld
ES_AUTOVSCROLL
Wenn der Anwender RETURN in der letzten Zeile eines mehrzeiligen Eingabefeldes eingibt, wird eine Seite weitergeblättert.
ES_WANTRETURN
Ein mehrzeiliges Eingabefeld kann so ein RETURN empfangen, das ansonsten an die Dialogbox ginge, um das Default-Element auszulösen.

Ansteuerung

WM_GETTEXT
wParam = max Zeichenzahl, lParam = Adresse des Puffers

lese den Inhalt des Eingabefeldes

WM_SETTEXT
wParam = 0, lParam = Adresse des Puffers

schreibe in den Inhalt des Eingabefeldes

EM_LIMITTEXT
wParam = max Zeichen, lParam = 0

begrenzt die Zahl der eingegebenen Zeichen.

WM_SETFONT
wParam = HFONT, lParam (boolean) Redraw

Der Zeichensatz des Elements wird umgeschaltet.

Ereignisse

EN_CHANGE
Änderung am Editfeld nachdem sie angezeigt werden.
EN_UPDATE
Änderung am Editfeld bevor sie angezeigt werden.

Beispiel

Diese Fensterfunktion erzeugt beim Start des Fensters ein Eingabefeld und merkt sich in der statischen Variablen EdHwnd das Handle. Die 1201 ist die (beliebige) ID des Editfeldes. Sie muss allerdings im Fenster eindeutig sein. Wird der Menüpunkt mit der ID M_SHOW (in der Ressource zu erstellen) gedrückt, erscheint der aktuelle Text des Eingabefeldes in der Messagebox. Der Menüpunkt IDM_EXIT sorgt nur für ein reguläres Beenden des Programmes.

[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04508
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16552
Queueeinträge:06246
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1154
Comments: 0