Вы можете расширить структуру PROPSHEETPAGE своими собственными дополнительными данными
... в тех случаях, когда возможностей обычного параметра lParam недостаточно.
Малоизвестная и еще реже используемая возможность окна свойств оболочки, которая заключается в том, что вы можете прикрепить собственные данные в конец структуры PROPSHEETPAGE, а оболочка операционной системы (shell) будет передавать их вам. Правда, она передает их при помощи операции memcpy и уничтожает их, просто освобождая память, занятую этими данными, поэтому единственное, что вы можете прикрепить к этой структуре, лишь простые типы данных (plain old data). (Однако вы можете получить возможность «конструировать» и «деконструировать» эти данные, если зарегистрируете обратный вызов типа PropSheetPageProc, в котором вы сможете изменять ваши дополнительные данные и поле lParam структуры PROPSHEETPAGE).
Вот программа, которая иллюстрирует данную технику. Она не делает особо интересных вещей, впрочем, возможно, это и к лучшему: меньше будет отвлекать нас от основной мысли.
#include <windows.h> #include <prsht.h> HINSTANCE g_hinst; struct ITEMPROPSHEETPAGE : public PROPSHEETPAGE { int cWidgets; TCHAR szItemName[100]; };
ITEMPROPSHEETPAGE — это наша собственная структура, которая присоединяет наши дополнительные данные (целое число и строку) к стандартной структуре PROPSHEETPAGE. Эту структуру будет использовать наша вкладка окна свойств.
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { switch (uiMsg) { case WM_INITDIALOG: { ITEMPROPSHEETPAGE *ppsp = reinterpret_cast<ITEMPROPSHEETPAGE*>(lParam)); SetDlgItemText(hwnd, 100, ppsp->szItemName); SetDlgItemInt(hwnd, 101, ppsp->cWidgets, FALSE); } return TRUE; } return FALSE; }
Значение lParam, переданное в обработчик сообщения WM_INITDIALOG, является указателем на структуру PROPSHEETPAGE, управляемую оболочкой. Поскольку мы ассоциировали эту процедуру диалогового окна со структурой ITEMPROPSHEETPAGE, мы можем выполнить приведение к расширенной структуре, чтобы получить наши дополнительные данные (которые оболочка благополучно скопировала при помощи операции memcpy из нашего экземпляра в экземпляр, управляемый оболочкой).
HPROPSHEETPAGE CreateItemPropertySheetPage( int cWidgets, PCTSTR pszItemName) { ITEMPROPSHEETPAGE psp; ZeroMemory(&psp, sizeof(psp)); psp.dwSize = sizeof(psp); psp.hInstance = g_hinst; psp.pszTemplate = MAKEINTRESOURCE(1); psp.pfnDlgProc = DlgProc; psp.cWidgets = cWidgets; lstrcpyn(psp.szItemName, pszItemName, 100); return CreatePropertySheetPage(&psp); }
Здесь мы ассоциируем процедуру DlgProc со структурой ITEMPROPSHEETPAGE. Для того чтобы подчеркнуть, что указатель, передаваемый в DlgProc, является копией структуры ITEMPROPSHEETPAGE, которая использовалась для создания вкладки окна свойств, я вынес код ее создания в отдельную функцию. Таким образом, содержимое структуры ITEMPROPSHEETPAGE, находящееся в стеке, выходит за пределы видимости, и становится очевидным, что экземпляр, переданный в DlgProc, не тот же самый экземпляр, который мы передали в функцию CreatePropertySheetPage.
Обратите внимание, что вы должны установить поле dwSize базовой структуры PROPSHEETPAGE равным сумме размеров структуры PROPSHEETPAGE и ваших дополнительных данных. Другими словами, вы должны установить это поле равным размеру вашей структуры ITEMPROPSHEETPAGE.
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { g_hinst = hinst; HPROPSHEETPAGE hpage = CreateItemPropertySheetPage(42, TEXT("Elmo")); if (hpage) { PROPSHEETHEADER psh = { 0 }; psh.dwSize = sizeof(psh); psh.dwFlags = PSH_DEFAULT; psh.hInstance = hinst; psh.pszCaption = TEXT("Item Properties"); psh.nPages = 1; psh.phpage = &hpage; PropertySheet(&psh); } return 0; }
Здесь мы отображаем вкладку окна свойств. Этот код выглядит так же, как и любой другой код, отображающий вкладку окна свойств. Вся магия заключается в способе создания структуры HPROPSHEETPAGE.
Если вы предпочитаете использовать флаг PSH_PROPSHEETPAGE, тогда вышеприведенный код должен быть переписан в таком виде:
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { ITEMPROPSHEETPAGE psp; ZeroMemory(&psp, sizeof(psp)); psp.dwSize = sizeof(psp); psp.hInstance = g_hinst; psp.pszTemplate = MAKEINTRESOURCE(1); psp.pfnDlgProc = DlgProc; psp.cWidgets = cWidgets; lstrcpyn(psp.szItemName, pszItemName, 100); PROPSHEETHEADER psh = { 0 }; psh.dwSize = sizeof(psh); psh.dwFlags = PSH_PROPSHEETPAGE; psh.hInstance = hinst; psh.pszCaption = TEXT("Item Properties"); psh.nPages = 1; psh.ppsp = &psp; PropertySheet(&psh); return 0; }
Если вместо одной страницы окна свойств вы хотите создать несколько страниц, вам нужно передать массив структур ITEMPROPSHEETPAGE. Обратите внимание, что передача массива требует, чтобы все страницы в массиве принимали одну и ту же дополнительную структуру данных (потому что так устроены массивы: все элементы в нем должны быть одного и того же типа).
И, наконец, шаблон диалогового окна. Еще скучнее.
1 DIALOG 0, 0, PROP_SM_CXDLG, PROP_SM_CYDLG STYLE WS_CAPTION | WS_SYSMENU CAPTION "General" FONT 8, "MS Shell Dlg" BEGIN LTEXT "Name:",-1,7,11,42,14 LTEXT "",100,56,11,164,14 LTEXT "Widgets:",-1,7,38,42,14 LTEXT "",101,56,38,164,14 END
Вот и все. Прикрепление своих собственных данных в конец структуры PROPSHEETPAGE является более эффективной альтернативой вместо попытки запихнуть все в единственный параметр lParam.
Упражнение: посмотрите, как со временем менялся размер структуры PROPSHEETPAGE. К примеру, изначальная версия структуры PROPSHEETPAGE заканчивалась полем pcRefParent. В Windows 2000 добавилось два новых поля: pszHeaderTitle и pszHeaderSubTitle. Windows XP добавила еще одно поле: hActCtx. Представьте, что вы написали программу для Windows 95 с использованием данной техники. Как оболочка узнает, что настоящие дополнительные данные начинаются с поля cWidgets, а не с pszHeaderTitle?