Как создать нестандартное окно сообщенияDelphi , Программа и Интерфейс , Диалоги и ФреймыКак создать нестандартное окно сообщения
Оформил: DeeCo Окна сообщения (Message Box) – это стандартные диалоговые окна, используемые в программах для информирования пользователя, предупреждения или уточнения его желаний. Типичное окно сообщения выглядит так:
Рисунок 1. Типичное окно собщения. Для вывода окна сообщения служит функция Windows API ::MessageBox(). intMessageBox (HWNDhWnd, LPCTSTRlpText, LPCTSTRlpCaption, UINTuType ); Параметр hWnd – это родительское окно. Как правило, это главное окно приложения. Если приложение не имеет окон (например, консольное приложение), этот параметр может быть равен NULL. Параметр lpText – это собственно текст сообщения. Параметр lpCaption – это заголовок окна сообщения. Если он равен NULL, используется строка "Ошибка". Параметр uType задает количество кнопок и другие параметры окна сообщения. С его помощью можно задать иконку слева от текста и такие свойства окна, как модальность (modality). К сожалению, этого иногда оказывается недостаточно. Например, нужна возможность подавления сообщения в будущем, что-то вроде:
Рисунок 2. Окно сообщения с 'галочкой'. Как же расширить возможности этой функции? Нестандартное окно сообщения Способ №1: диалоговое окноПервое, что приходит на ум – создать диалоговое окно, и расставить на нем все нужные кнопки. Это наиболее простой способ. 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); Но, к сожалению, это и наиболее трудоемкий способ. Все эти диалоги нужно сначала нарисовать. Кроме того, каждое из таких "неуниверсальных" диалоговых окон увеличивает размер программы. Способ №2: универсальное диалоговое окноЕсли программе нужно выводить большое количество сообщений, и ::MessageBox() по каким-либо причинам не подходит, можно написать свой аналог. Для этого понадобится заготовка – небольшой диалог со всеми кнопками, которые могут понадобиться, и двумя полями для текста и иконки, плюс немного кода, чтобы "спрятать" лишние кнопки и настроить текстовое поле и иконку. Листинг 1. Код инициализации диалога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 окна сообщения, которое мы и будем использовать в дальнейшем. Листинг 2. Код, добавляющий 'галочку' в стандартное окно сообщения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 не будет, например, двух иконок, или кнопок сверху. Статья Как создать нестандартное окно сообщения раздела Программа и Интерфейс Диалоги и Фреймы может быть полезна для разработчиков на Delphi и FreePascal. Комментарии и вопросы:: 2012-04-07 23:04:22 :: re:Как создать нестандартное окно сообщенияпользователь: Михаил. Как программно послать сообщение, не требующее ответа? И как его, опять же программно, удалить с экрана? Вывод в Label.Caption, например, ждет завершения дисковых операций, а сообщения – нет, вроде бы. Нужно для оперативной информации выполнения программы. Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта. :: Главная :: Диалоги и Фреймы ::
|
||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |