Windows Programmierung: Property Sheet
Willemers Informatik-Ecke
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.