ПРИМЕЧАНИЕ
Все классы, показанные на рисунке 1, WTL унаследовала от библиотеки ATL. Они описаны в файле atlwin.h
Теперь изучим каждый класс более подробно.
Класс CDialogImplBaseT<>Итак, класс CDialogImplBaseT<> содержит функциональность, необходимую всем без исключения диалоговым окнам. Это, в первую очередь, поддержка диалоговых процедур, а также пара вспомогательных функций. Обратите внимание, что в класс CDialogImplBaseT<> не встроен механизм создания диалога при помощи функций DialogBox и CreateDialog. Дело в том, что не все диалоги нуждаются в этих функциях. Например, стандартные диалоги создаются при помощи специальных функций (GetOpenFileName, ChooseColor и т. д.).
Диалоговые процедуры в классе CDialogImplBaseT<> реализованы более или менее аналогично оконным процедурам в классе CWindowImplBaseT<>.
template <class TBase>
LRESULT CALLBACK CDialogImplBaseT<TBase>::StartDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
CDialogImplBaseT<TBase>* pThis = (CDialogImplBaseT<TBase>*)_Module.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
pThis->m_hWnd = hWnd;
pThis->m_thunk.Init(pThis->GetDialogProc(), pThis);
WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);
WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, DWL_DLGPROC, (LONG)pProc);
#ifdef _DEBUG
// check if somebody has subclassed us already since we discard it
if (pOldProc != StartDialogProc)
ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.n"));
#else
pOldProc; // avoid unused warning
#endif
return pProc(hWnd, uMsg, wParam, lParam);
}
template <class TBase>LRESULT CALLBACK CDialogImplBaseT<TBase>::DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
CDialogImplBaseT<TBase>* pThis = (CDialogImplBaseT<TBase>*)hWnd;
// set a ptr to this message and save the old value
MSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };
const MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// pass to the message map to process
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
pThis->m_pCurrentMsg = pOldMsg;
// set result if message was handled
if (bRet) {
switch (uMsg) {
case WM_COMPAREITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_INITDIALOG:
case WM_QUERYDRAGICON:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
return lRes;
break;
}
::SetWindowLong(pThis->m_hWnd, DWL_MSGRESULT, lRes);
return TRUE;
}
if (uMsg == WM_NCDESTROY) {
// clear out window handle
HWND hWnd = pThis->m_hWnd;
pThis->m_hWnd = NULL;
// clean up after dialog is destroyed
pThis->OnFinalMessage(hWnd);
}
return FALSE;
}
Статическая функция StartDialogProc назначается диалогу при его создании. Для этого её адрес передаётся функциям, подобным DialogBox и CreateDialog, или задаётся в качестве хука для стандартных диалогов. Получив управление, эта функция извлекает хэндл диалога из объекта _Module и сохраняет его в переменной m_hWnd, затем инициализирует переходник и передаёт управление штатной диалоговой процедуре DialogProc, которая и выполняет дальнейшее обслуживание диалога. Каждое полученное сообщение она "пропускает" через карту сообщений вызовом ProcessWindowMessage. Возвращаемое после обработки сообщения значение интерпретируется в зависимости от типа сообщения. Тем самым обеспечивается небольшое, но весьма приятное удобство: программист не должен помнить, каким образом нужно передать операционной системе LRESULT из диалоговой процедуры (напрямую или с помощью SetWindowLong). Достаточно вернуть его из функции-обработчика, а об остальном позаботится WTL.
Поскольку диалоги, как и все остальные окна, используют для обработки сообщений функцию ProcessWindowMessage, вы можете использовать для её создания уже знакомые вам макросы карты сообщений.
После уничтожения диалога WTL вызывает виртуальную функцию OnFinalMessage. Вы можете переопределить её в производном классе и возложить на неё "очистительные" работы. Следует только иметь в виду, что во время работы этой функции диалог уже не существует, и даже переменная m_hWnd содержит NULL. Поэтому в функции OnFinalMessage нельзя, к примеру, загружать данные из контролов диалога в переменные.
Класс CDialogImpl<>Класс CDialogImpl<> – основное средство для работы с диалогами в WTL. Он используется как с модальными, так и с немодальными диалогами. Соответственно, в нём содержатся обёртки для функций DialogBoxParam, EndDialog, CreateDialogParam и DestroyWindow. Механизм обработки сообщений наследуется от класса CDialogImplBaseT<>.
Для создания модального диалога используется метод DoModal. Уничтожить модальный диалог можно, используя метод EndDialog (можно вызывать этот метод из любого обработчика сообщений, в том числе из обработчика сообщения WM_INITDIALOG). Реализация обоих методов более чем прямолинейна:
// modal dialogs
int DoModal(HWND hWndParent = ::GetActiveWindow(), LPARAM dwInitParam = NULL) {
ATLASSERT(m_hWnd == NULL);
_Module.AddCreateWndData(&m_thunk.cd, (CDialogImplBaseT<TBase>*)this);
#ifdef _DEBUG
m_bModal = true;
#endif //_DEBUG
return
::DialogBoxParam(_Module.GetResourceInstance(), MAKEINTRESOURCE(T::IDD),
hWndParent, (DLGPROC)T::StartDialogProc, dwInitParam);
}
BOOL EndDialog(int nRetCode) {
ATLASSERT(::IsWindow(m_hWnd));
ATLASSERT(m_bModal); // must be a modal dialog
return ::EndDialog(m_hWnd, nRetCode);
}
Здесь следует обратить внимание всего на две вещи. Во-первых, в качестве диалоговой процедуры задаётся StartDialogProc. Благодаря этому к создаваемому диалогу подключается механизм обработки сообщений, рассмотренный в предыдущем разделе. Во-вторых, в качестве идентификатора ресурса диалога используется константа IDD. Вам необходимо определить её в производном классе, чтобы WTL знала, какой диалог требуется создать. В принципе, можно сделать IDD и статической переменной производного класса, но прибегать к этому приёму на практике приходится не часто.
ПРИМЕЧАНИЕ
Библиотека MFC не использует функцию DialogBox(Param). Вместо этого она создаёт немодальный диалог, а затем эмулирует поведение модального. Благодаря этому программировать модальные диалоги в MFC гораздо удобнее, чем на "чистом" Win32 API (а значит, и в WTL). Проблема в том, что функция DialogBox(Param) создаёт свой собственный цикл сообщений, до которого не так-то просто добраться. Если нам потребуется, к примеру, внедрить в него трансляцию акселераторов, придётся прибегать к различным неочевидным приёмам.
Немодальный диалог создаётся с использованием функции Create и разрушается вызовом DestroyWindow. Реализация обоих методов также достаточно очевидна.
// modeless dialogs
HWND Create(HWND hWndParent, LPARAM dwInitParam = NULL) {
ATLASSERT(m_hWnd == NULL);
_Module.AddCreateWndData(&m_thunk.cd, (CDialogImplBaseT<TBase>*)this);
#ifdef _DEBUG
m_bModal = false;
#endif //_DEBUG
HWND hWnd = ::CreateDialogParam(_Module.GetResourceInstance(), MAKEINTRESOURCE(T::IDD),
hWndParent, (DLGPROC)T::StartDialogProc, dwInitParam);
ATLASSERT(m_hWnd == hWnd);
return hWnd;
}
BOOL DestroyWindow() {
ATLASSERT(::IsWindow(m_hWnd));
ATLASSERT(!m_bModal); // must not be a modal dialog
return ::DestroyWindow(m_hWnd);
}
С учётом всего сказанного, типичный класс диалога, порождённый от CDialogImpl<>, выглядит так (в качестве параметра шаблона задаётся имя класса, который вы порождаете).
class CMyDialog : public CDialogImpl<CMyDialog> {
public:
enum { IDD = IDIDD_MY_DIALOG };
BEGIN_MSG_MAP(CMyDialog)
// Карта сообщений
END_MSG_MAP()
};
Обратите внимание, что константа IDD описывается в секции public. Если описать её в private-секции, функция базового класса CDialogImpl<>::DoModal не сможет к ней обратиться, что приведёт к ошибке.
Далее полученный класс можно использовать для создания как модальных, так и немодальных диалогов, например:
// Создаём модальный диалог
CMyDialog modal;
modal.DoModal();
// Создаём немодальный диалог
CMyDialog modeless;
modeless.Create(HWND_DESKTOP);
Класс CAxDialogImpl<>Класс CAxDialogImpl<> очень похож на предыдущий. Вся разница в том, что вместо функции DialogBoxParam он использует функцию AtlAxDialogBox, а вместо функции CreateDialogParam – функцию AtlAxCreateDialog: