| 
 Как создать нестандартное окно сообщения
 
 
Оформил: DeeCoВведениеАвтор: Paul Bludov
 Окна сообщения (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. Код инициализации диалога 
Способ №3: Настоящий 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 окна сообщения, которое мы и будем использовать в дальнейшем.  Листинг 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 не будет, например, двух иконок, или кнопок 
сверху. |