Выпуск
No. 95 от 23 июня 2003 г. Подписчиков: 21698
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО
ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ,
РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Здравствуйте! CТАТЬЯ
Message Box и немного фантазии Как создать нестандартное окно сообщения
Окна сообщения (Message Box) – это стандартные диалоговые окна, используемые в программах для информирования пользователя, предупреждения или уточнения его желаний. Типичное окно сообщения выглядит так:
Параметр hWnd – это родительское окно. Как правило, это главное окно приложения. Если приложение не имеет окон (например, консольное приложение), этот параметр может быть равен NULL.
Параметр lpText – это собственно текст сообщения.
Параметр lpCaption – это заголовок окна сообщения. Если он равен NULL, используется строка "Ошибка".
Параметр uType задает количество кнопок и другие параметры окна сообщения. С его помощью можно задать иконку слева от текста и такие свойства окна, как модальность (modality).
К сожалению, этого иногда оказывается недостаточно. Например, нужна возможность подавления сообщения в будущем, что-то вроде:
Но, к сожалению, это и наиболее трудоемкий способ. Все эти диалоги нужно сначала нарисовать. Кроме того, каждое из таких "неуниверсальных" диалоговых окон увеличивает размер программы.
Способ №2: универсальное диалоговое окно
Если программе нужно выводить большое количество сообщений, и ::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 не будет, например, двух иконок, или кнопок сверху.
Ведущий рассылки: Алекс Jenterjenter@rsdn.ru
Публикуемые в рассылке материалы принадлежат сайту RSDN.