Сообщений 21 Оценка 445 Оценить |
Введение Нестандартное окно сообщения |
Демонстрационный проект MBTest (WinAPI, 14.5k)
Окна сообщения (Message Box) – это стандартные диалоговые окна, используемые в программах для информирования пользователя, предупреждения или уточнения его желаний. Типичное окно сообщения выглядит так:
Рисунок 1. Типичное окно собщения.
Для вывода окна сообщения служит функция Windows API ::MessageBox().
int MessageBox
( HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
|
Параметр hWnd – это родительское окно. Как правило, это главное окно приложения. Если приложение не имеет окон (например, консольное приложение), этот параметр может быть равен NULL.
Параметр lpText – это собственно текст сообщения.
Параметр lpCaption – это заголовок окна сообщения. Если он равен NULL, используется строка "Ошибка".
Параметр uType задает количество кнопок и другие параметры окна сообщения. С его помощью можно задать иконку слева от текста и такие свойства окна, как модальность (modality).
К сожалению, этого иногда оказывается недостаточно. Например, нужна возможность подавления сообщения в будущем, что-то вроде:
Рисунок 2. Окно сообщения с 'галочкой'.
Как же расширить возможности этой функции?
Первое, что приходит на ум – создать диалоговое окно, и расставить на нем все нужные кнопки. Это наиболее простой способ.
INT_PTR CALLBACK _CustomDialogProc ( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if (WM_COMMAND == uMsg) ::EndDialog(hwndDlg, LOWORD(wParam)); return FALSE; } int nRet = ::DialogBoxParam(hInstance, MAKEINTRESOURCE(ID_CUSTOMDIALOG), NULL, _CustomDialogProc, 0); |
Но, к сожалению, это и наиболее трудоемкий способ. Все эти диалоги нужно сначала нарисовать. Кроме того, каждое из таких "неуниверсальных" диалоговых окон увеличивает размер программы.
Если программе нужно выводить большое количество сообщений, и ::MessageBox() по каким-либо причинам не подходит, можно написать свой аналог.
Для этого понадобится заготовка – небольшой диалог со всеми кнопками, которые могут понадобиться, и двумя полями для текста и иконки, плюс немного кода, чтобы "спрятать" лишние кнопки и настроить текстовое поле и иконку.
LRESULT _CustomMessageBoxInit(HWND hwndDlg, _SCustomMessageBoxParam *pInit) { // Расстояние между кнопками, а также бордюр const int nBorder = 11; UINT uType = pInit->m_uType; RECT rect; RECT rectButton; int nVisibleButtons = 0; int nVisibleButtonsWidth = 0; HDC hdcDlg; HWND hwndText = ::GetDlgItem(hwndDlg, ID_MSGBOXTEXT); // Заголовок окна if (pInit->m_lpCaption) ::SetWindowText(hwndDlg, pInit->m_lpCaption); // Текст окна ::SetWindowText(hwndText, pInit->m_lpText); // Включаем нужные кнопки nVisibleButtons = _CustomMessageBoxShowButtons(hwndDlg, uType); // Устанавливаем иконку _CustomMessageBoxSetIcon(hwndDlg, uType); // Подсчитываем размер текста ::GetClientRect(hwndText, &rect); rect.top = rect.left = nBorder; rect.right += nBorder; rect.bottom = 0; hdcDlg = ::GetWindowDC(hwndDlg); ::DrawText(hdcDlg, pInit->m_lpText, -1, &rect, DT_LEFT | DT_EXPANDTABS | DT_WORDBREAK | DT_CALCRECT); ::ReleaseDC(hwndDlg, hdcDlg); ::SetWindowPos(hwndText, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, ((MB_ICONMASK & uType) ? SWP_NOMOVE : 0 ) | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); if (MB_ICONMASK & uType) { int nIconHeight = ::GetSystemMetrics(SM_CYICON); if (rect.bottom - rect.top < nIconHeight) rect.bottom = rect.top + nIconHeight; } // Расставляем кнопки ::GetClientRect(::GetDlgItem(hwndDlg, IDOK), &rectButton); nVisibleButtonsWidth = (nVisibleButtons * (rectButton.right + nBorder)); if (rect.right < nVisibleButtonsWidth) { rect.right = nVisibleButtonsWidth; _CustomMessageBoxInitPositionButtons(hwndDlg, nBorder, rect.bottom, nBorder + rectButton.right, (uType & MB_DEFMASK) >> 8); } else { _CustomMessageBoxInitPositionButtons(hwndDlg, (rect.right - nVisibleButtonsWidth) / 2, rect.bottom, nBorder + rectButton.right, (uType & MB_DEFMASK) >> 8); } // Пересчитываем размеры самого диалога rect.right += nBorder * 2; rect.bottom += (rectButton.bottom + nBorder * 2); ::AdjustWindowRectEx(&rect, ::GetWindowLong(hwndDlg, GWL_STYLE) , FALSE, ::GetWindowLong(hwndDlg, GWL_EXSTYLE)); _CenterWindow(hwndDlg, &rect); return 0; } |
Оба предыдущих способа имеют ряд недостатков: Во-первых, никто не знает, как будут выглядеть окна сообщений в следующей версии Windows. Возможно, у них будут четыре дополнительных кнопки в заголовке или кнопки зеленого цвета. Наши же диалоги будут выглядеть нормально – как и положено диалогам. Во-вторых, эти способы не содержат кода для поддержки таких режимов стандартных окон сообщений, как MB_TASKMODAL. В этом случае, можно воспользоваться хуками Windows.
СОВЕТ Подробнее о хуках можно прочитать на http://www.rsdn.ru/article/?baseserv/winhooks.xml |
Все, что нужно – это установить локальный хук, вызвать ::MessageBox(), выполнить в обработчике хука все необходимые действия и снять хук по завершении ::MessageBox().
Тут имеется небольшая проблема: стандартное окно сообщения использует локальный цикл обработки сообщений (message pump), и окон, появившихся в результате вызова ::MessageBox(), может быть несколько. На самом деле все не так плохо: первое оповещение типа HCBT_CREATEWND, пришедшее в наш обработчик, даст нам HWND окна сообщения, которое мы и будем использовать в дальнейшем.
class CMessageBoxPatcher : public CThunk<CMessageBoxPatcher, HOOKPROC> { BOOL CalcCheckBoxRect ( RECT *prectCheckBox , int *nGap ) { HWND hwndTextOrIcon; RECT rectTmp; // Ищем иконку или текст, если иконки нет hwndTextOrIcon = ::FindWindowEx(m_hwndMessageBox, NULL, _T("STATIC"), NULL); if (!hwndTextOrIcon) return FALSE; if (!::GetWindowRect(hwndTextOrIcon, &rectTmp)) return FALSE; // Тут мы получили .left, отступ по вертикали, и, возможно, .bottom prectCheckBox->left = rectTmp.left; ::MapWindowPoints(NULL, m_hwndMessageBox, (LPPOINT)&rectTmp, 1); *nGap = rectTmp.top; prectCheckBox->bottom = rectTmp.bottom; // Ищем текст (если до этого нашли иконку) hwndTextOrIcon = ::FindWindowEx(m_hwndMessageBox, hwndTextOrIcon , _T("STATIC"), NULL); if (hwndTextOrIcon && !::GetWindowRect(hwndTextOrIcon, &rectTmp)) return FALSE; // получили .right && .bottom prectCheckBox->right = rectTmp.right; if (rectTmp.bottom > prectCheckBox->bottom) prectCheckBox->bottom = rectTmp.bottom; // Теперь нужно рассчитать размер текста и галочки HDC hdcMessageBox = ::GetWindowDC(m_hwndMessageBox); if (!hdcMessageBox) return FALSE; rectTmp.left = ::GetSystemMetrics(SM_CXMENUCHECK); rectTmp.right -= prectCheckBox->left; rectTmp.top = 0; rectTmp.bottom = 0x4000; ::DrawText(hdcMessageBox, m_lpCheckBoxString, -1, &rectTmp, DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX); ::ReleaseDC(m_hwndMessageBox, hdcMessageBox); // Получили .top prectCheckBox->top = prectCheckBox->bottom - rectTmp.bottom; return ::MapWindowPoints(NULL, m_hwndMessageBox, (LPPOINT)prectCheckBox, 2); } HWND InsetCheckBox() { RECT rectCheckBox; RECT rectWindow; int nHeightGrow; HWND hwndCheckBox = NULL; if (!CalcCheckBoxRect(&rectCheckBox, &nHeightGrow)) return NULL; // Создаем галочку hwndCheckBox = ::CreateWindowEx(WS_EX_NOPARENTNOTIFY, _T("BUTTON"), m_lpCheckBoxString, BS_LEFT | BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP | WS_CHILD | WS_VISIBLE, rectCheckBox.left, rectCheckBox.top, rectCheckBox.right - rectCheckBox.left, rectCheckBox.bottom - rectCheckBox.top, m_hwndMessageBox, NULL, NULL, 0); if (hwndCheckBox) { // Устанавливаем нужный шрифт ::SendMessage(hwndCheckBox, WM_SETFONT, ::SendMessage(m_hwndMessageBox, WM_GETFONT, 0, 0), FALSE); // Выставляем начальное состояние if (m_bNoMore) ::SendMessage(hwndCheckBox, BM_SETCHECK, BST_CHECKED, 0); } // Увеличиваем окно и сдвигаем все кнопки вниз if (::GetWindowRect(m_hwndMessageBox, &rectWindow)) { nHeightGrow += (rectCheckBox.bottom - rectCheckBox.top); ::SetWindowPos(m_hwndMessageBox, NULL, 0, 0, rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top + nHeightGrow, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW); MoveButtonsDown(nHeightGrow); } return m_hwndCheckBox = hwndCheckBox; } void MoveButtonsDown ( int nDistance ) { HWND hwndButton = NULL; RECT rectButton; while (hwndButton = ::FindWindowEx(m_hwndMessageBox, hwndButton, _T("BUTTON"), NULL), hwndButton) { ::GetWindowRect(hwndButton, &rectButton); ::MapWindowPoints(NULL, m_hwndMessageBox, (LPPOINT)&rectButton, 2); ::SetWindowPos(hwndButton, NULL, rectButton.left, rectButton.top + nDistance, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW); } } bool IsOurWindow ( HWND hwnd ) const { ATLASSERT(m_hwndMessageBox); return m_hwndMessageBox == hwnd; } LRESULT CBTProc ( int nCode, WPARAM wParam, LPARAM lParam ) { HWND hwnd = (HWND)wParam; if (HCBT_CREATEWND == nCode && !m_hwndMessageBox) m_hwndMessageBox = hwnd; else if (HCBT_ACTIVATE == nCode && !m_hwndCheckBox && IsOurWindow(hwnd)) InsetCheckBox(); else if (HCBT_DESTROYWND == nCode && IsOurWindow(hwnd)) m_bNoMore = (BST_CHECKED == ::SendMessage(m_hwndCheckBox, BM_GETCHECK, 0, 0)); return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); } public: CMessageBoxPatcher ( LPCTSTR lpCheckBoxString, bool bNoMoreByDefault = false ) : CThunk<CMessageBoxPatcher, HOOKPROC>((TMFP)CBTProc, this), m_bNoMore(bNoMoreByDefault), m_lpCheckBoxString(lpCheckBoxString), m_hwndCheckBox(NULL), m_hwndMessageBox(NULL) { m_hHook = ::SetWindowsHookEx(WH_CBT, GetThunk(), NULL, ::GetCurrentThreadId()); } ~CMessageBoxPatcher() { if (m_hHook) ::UnhookWindowsHookEx(m_hHook); } bool GetBoxState() const { return m_bNoMore; } private: HHOOK m_hHook; HWND m_hwndCheckBox; HWND m_hwndMessageBox; bool m_bNoMore; LPCTSTR m_lpCheckBoxString; }; inline int WINAPI MessageBox ( IN HWND hwnd, IN LPCTSTR lpText, IN LPCTSTR lpCaption, IN UINT uType, IN LPCTSTR lpCheckBoxString, IN OUT PBOOL pbNoMore ) { CMessageBoxPatcher patcher(lpCheckBoxString, !!*pbNoMore); int nRet; nRet = ::MessageBox(hwnd, lpText, lpCaption, uType); *pbNoMore = patcher.GetBoxState(); return nRet; } |
ПРИМЕЧАНИЕ Чтобы "превратить" обработчик хука в функцию-член класса, в данном примере используется механизм переходников, thunks. |
100% гарантии не дает и этот способ: он рассчитан на то, что у окна сообщения в следующей версии Windows не будет, например, двух иконок, или кнопок сверху.
Сообщений 21 Оценка 445 Оценить |