Окна сообщения (Message Box) – это стандартные диалоговые окна, используемые
в программах для информирования пользователя, предупреждения или уточнения его
желаний. Типичное окно сообщения выглядит так:
Рисунок 1. Типичное окно собщения.
Для вывода окна сообщения служит функция Windows API
::MessageBox().
Параметр hWnd – это родительское окно. Как правило, это
главное окно приложения. Если приложение не имеет окон (например, консольное
приложение), этот параметр может быть равен NULL.
Параметр lpText – это собственно текст сообщения.
Параметр lpCaption – это заголовок окна сообщения. Если он
равен NULL, используется строка "Ошибка".
Параметр uType задает количество кнопок и другие параметры
окна сообщения. С его помощью можно задать иконку слева от текста и такие
свойства окна, как модальность (modality).
К сожалению, этого иногда оказывается недостаточно. Например, нужна
возможность подавления сообщения в будущем, что-то вроде:
Но, к сожалению, это и наиболее трудоемкий способ. Все эти диалоги нужно
сначала нарисовать. Кроме того, каждое из таких "неуниверсальных" диалоговых
окон увеличивает размер программы.
Способ №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;
}
Способ №3: Настоящий MessageBox + хук.
Оба предыдущих способа имеют ряд недостатков: Во-первых, никто не знает, как
будут выглядеть окна сообщений в следующей версии Windows. Возможно, у них будут
четыре дополнительных кнопки в заголовке или кнопки зеленого цвета. Наши же
диалоги будут выглядеть нормально – как и положено диалогам. Во-вторых, эти
способы не содержат кода для поддержки таких режимов стандартных окон сообщений,
как MB_TASKMODAL. В этом случае, можно воспользоваться хуками Windows.
Все, что нужно – это установить локальный хук, вызвать ::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 не будет, например, двух иконок, или кнопок
сверху.
Если Вас заинтересовала или понравилась информация по разработке на Delph - "Как создать нестандартное окно сообщения", Вы можете поставить закладку в социальной сети или в своём блоге на данную страницу: Так же Вы можете задать вопрос по работе этого модуля или примера через форму обратной связи, в сообщение обязательно указывайте название или ссылку на статью!