2 * Extended MFC message boxes -- Version 1.1a
3 * Copyright (c) 2004 Michael P. Mehl. All rights reserved.
5 * The contents of this file are subject to the Mozilla Public License
6 * Version 1.1a (the "License"); you may not use this file except in
7 * compliance with the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/.
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Copyright (c) 2004 Michael P. Mehl. All rights
16 * reserved. The Initial Developer of the Original Code is Michael P. Mehl
17 * <michael.mehl@web.de>.
19 * Alternatively, the contents of this file may be used under the terms of
20 * the GNU Lesser General Public License Version 2.1 (the "LGPL License"),
21 * in which case the provisions of LGPL License are applicable instead of
22 * those above. If you wish to allow use of your version of this file only
23 * under the terms of the LGPL License and not to allow others to use your
24 * version of this file under the MPL, indicate your decision by deleting
25 * the provisions above and replace them with the notice and other provisions
26 * required by the LGPL License. If you do not delete the provisions above,
27 * a recipient may use your version of this file under either the MPL or
32 * The flag MB_DONT_DISPLAY_AGAIN or MB_DONT_ASK_AGAIN is stored in the registry
33 * See GenerateRegistryKey for the creation of the key
34 * The "normal" rule is to use the help Id as identifier
35 * And it is really simple to just repeat the text ID as help ID
36 * (for message formed with AfxFormatString, repeat the ID used to format the string)
38 * Search for MB_DONT_DISPLAY_AGAIN and MB_DONT_ASK_AGAIN for all the
39 * concerned AfxMessageBox
44 #include "MessageBoxDialog.h"
52 IMPLEMENT_DYNAMIC(CMessageBoxDialog, CDialog)
54 //////////////////////////////////////////////////////////////////////////////
55 // Layout values (in dialog units).
57 #define CX_BORDER 8 // Width of the border.
58 #define CY_BORDER 8 // Height of the border.
60 #define CX_CHECKBOX_ADDON 14 // Additional width of the checkbox.
62 #define CX_BUTTON 51 // Standard width of a button.
63 #define CY_BUTTON 15 // Standard height of a button.
64 #define CX_BUTTON_BORDER 4 // Standard border for a button.
65 #define CY_BUTTON_BORDER 1 // Standard border for a button.
66 #define CX_BUTTON_SPACE 4 // Standard space for a button.
68 #define CX_DLGUNIT_BASE 1000 // Values used for converting
69 #define CY_DLGUNIT_BASE 1000 // dialog units to pixels.
71 //////////////////////////////////////////////////////////////////////////////
74 #define MESSAGE_BOX_TIMER 2201 // Event identifier for the timer.
76 //////////////////////////////////////////////////////////////////////////////
77 // Constructors and destructors of the class.
80 * Constructor of the class.
82 * This constructor is used to provide the strings directly without providing
83 * resource IDs from which these strings should be retrieved. If no title is
84 * given, the application name will be used as the title of the dialog.
86 CMessageBoxDialog::CMessageBoxDialog ( CWnd* pParent, CString strMessage,
87 CString strTitle, UINT nStyle, UINT nHelp, const CString& strRegistryKey )
88 : CDialog ( CMessageBoxDialog::IDD, pParent )
89 , m_strMessage(strMessage)
90 , m_strTitle(strTitle.IsEmpty() ? AfxGetAppName() : strTitle)
94 , m_nTimeoutSeconds(0)
95 , m_bTimeoutDisabled(false)
97 , m_strRegistryKey(strRegistryKey)
98 , m_nDefaultButton(IDC_STATIC)
99 , m_nEscapeButton(IDC_STATIC)
100 , m_sDialogUnit(CSize(0, 0))
101 , m_sIcon(CSize(0, 0))
102 , m_sMessage(CSize(0, 0))
103 , m_sCheckbox(CSize(0, 0))
104 , m_sButton(CSize(0, 0))
106 // Enable the active accessibility.
107 ASSERT(!strMessage.IsEmpty());
111 NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
112 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0);
113 m_font.CreateFontIndirect(&ncm.lfMessageFont);
116 HTHEME hTheme = OpenThemeData(nullptr, _T("TEXTSTYLE"));
117 if (hTheme != nullptr && SUCCEEDED(GetThemeFont(hTheme, nullptr, TEXT_MAININSTRUCTION, 0, TMT_FONT, &lf)))
119 m_fontMainInstruction.CreateFontIndirect(&lf);
120 GetThemeColor(hTheme, TEXT_MAININSTRUCTION, 0, TMT_TEXTCOLOR, &m_clrMainInstructionFont);
121 CloseThemeData(hTheme);
125 m_fontMainInstruction.CreateFontIndirect(&ncm.lfMessageFont);
126 m_clrMainInstructionFont = GetSysColor(COLOR_WINDOWTEXT);
131 * Constructor of the class.
133 * This constructor is used to load the strings for the title and the message
134 * text from the resources of this project. If no title is given, the
135 * application name will be used as the title of the dialog.
137 CMessageBoxDialog::CMessageBoxDialog ( CWnd* pParent, UINT nMessageID,
138 UINT nTitleID, UINT nStyle, UINT nHelp, const CString& strRegistryKey )
139 : CMessageBoxDialog(pParent,
140 LoadResString(nMessageID).c_str(),
141 nTitleID == 0 ? AfxGetAppName() : LoadResString(nTitleID).c_str(),
142 nStyle, nHelp, strRegistryKey )
147 * Destructor of the class.
149 CMessageBoxDialog::~CMessageBoxDialog ( )
153 //////////////////////////////////////////////////////////////////////////////
154 // Methods for setting and retrieving dialog options.
157 * Method for setting the style of the message box.
159 inline void CMessageBoxDialog::SetStyle ( UINT nStyle )
161 // Set the style of the message box.
166 * Method for retrieving the style of the message box.
168 inline UINT CMessageBoxDialog::GetStyle ( )
170 // Return the current style of the message box.
175 * Method for setting the message to be displayed in the message box.
177 inline void CMessageBoxDialog::SetMessage ( LPCTSTR strMessage )
179 ASSERT(*strMessage != '\0');
181 // Save the message text.
182 m_strMessage = strMessage;
186 * Methods for setting the message to be displayed in the message box.
188 inline void CMessageBoxDialog::SetMessage ( UINT nMessageID )
190 // Load the message from the resources.
191 m_strMessage = LoadResString(nMessageID);
192 ASSERT(!m_strMessage.empty());
196 * Method for retrieving the message to be displayed in the message box.
198 inline const String &CMessageBoxDialog::GetMessage ( )
200 // Return the message text.
205 * Method for setting the title to be displayed in the message box.
207 inline void CMessageBoxDialog::SetTitle ( LPCTSTR strTitle )
209 // Check whether a title was given.
210 if ( *strTitle == '\0' )
212 // Use the application name as the title.
213 strTitle = AfxGetAppName();
217 m_strTitle = strTitle;
221 * Method for setting the title to be displayed in the message box.
223 inline void CMessageBoxDialog::SetTitle ( UINT nTitleID )
225 // Check whether an ID was given.
228 // Use the application name as the title.
229 m_strTitle = AfxGetAppName();
233 // Try to load the string from the resources.
234 m_strTitle = LoadResString(nTitleID);
235 ASSERT(!m_strTitle.empty());
240 * Method for retrieving the title to be displayed in the message box.
242 inline const String &CMessageBoxDialog::GetTitle ( )
244 // Return the title of the message box.
249 * Method for setting the icon to be displayed in the message box.
251 inline void CMessageBoxDialog::SetMessageIcon ( HICON hIcon )
253 ASSERT(hIcon != nullptr);
260 * Method for setting the icon to be displayed in the message box.
262 inline void CMessageBoxDialog::SetMessageIcon ( UINT nIconID )
264 // Try to load the given icon.
265 m_hIcon = AfxGetApp()->LoadIcon(nIconID);
267 ASSERT(m_hIcon != nullptr);
271 * Method for retrieving the icon to be displayed in the message box.
273 inline HICON CMessageBoxDialog::GetMessageIcon ( )
275 // Return the icon for the message box.
280 * Method for setting a timeout.
282 * A timeout is a countdown, which starts, when the message box is displayed.
283 * There are two modes for a timeout: The "un-disabled" or "enabled" timeout
284 * means, that the user can choose any button, but if he doesn't choose one,
285 * the default button will be assumed as being chossen, when the countdown is
286 * finished. The other mode, a "disabled" countdown is something like a nag
287 * screen. All buttons will be disabled, until the countdown is finished.
288 * After that, the user can click any button.
290 void CMessageBoxDialog::SetTimeout ( UINT nSeconds, bool bDisabled /*= false*/)
292 // Save the settings for the timeout.
293 m_nTimeoutSeconds = nSeconds;
294 m_bTimeoutDisabled = bDisabled;
298 * Method for retrieving the seconds for a timeout.
300 inline UINT CMessageBoxDialog::GetTimeoutSeconds ( )
302 // Return the seconds for the timeout.
303 return m_nTimeoutSeconds;
307 * Method for retrieving whether a timeout is disabled.
309 inline bool CMessageBoxDialog::GetTimeoutDisabled ( )
311 // Return the flag whether the timeout is disabled.
312 return m_bTimeoutDisabled;
315 //////////////////////////////////////////////////////////////////////////////
316 // Methods for handling the stored states.
319 * Method for resetting the message boxes stored in the registry.
321 * This method removes all results of formerly displayed message boxes from
322 * the registry and therefore resets the state of the message boxes. Even
323 * those, where the user checked "Don't display/ask again" will again be
326 void CMessageBoxDialog::ResetMessageBoxes ( )
328 // Try to retrieve a handle to the application object.
329 CWinApp* pApplication = AfxGetApp();
331 ASSERT(pApplication != nullptr);
333 // Check whether a handle was retrieved.
334 if ( pApplication != nullptr )
336 // Delete the message box results stored in the registry.
337 pApplication->WriteProfileString(REGISTRY_SECTION_MESSAGEBOX, nullptr, nullptr);
341 CString CMessageBoxDialog::GenerateRegistryKey(UINT nMessageID, UINT nHelpID)
343 CMessageBoxDialog dlg{ nullptr, nMessageID, 0, 0, nHelpID };
344 return dlg.GenerateRegistryKey();
347 //////////////////////////////////////////////////////////////////////////////
348 // Methods for handling common window functions.
351 * Parameters for passing to ModelessMessageBoxThread.
353 struct ModelessMesssageBoxParam
360 * Thread for displaying the message boxes asyncronously.
362 static UINT ModelessMesssageBoxThread(LPVOID lpParam)
364 struct ModelessMesssageBoxParam *p = (struct ModelessMesssageBoxParam *)lpParam;
366 // Create the message box dialog.
367 CMessageBoxDialog dlgMessage(nullptr, p->strMessage, _T(""), p->nType);
371 // Display the message box dialog
372 dlgMessage.Create(CMessageBoxDialog::IDD);
373 dlgMessage.ShowWindow(SW_SHOW);
374 dlgMessage.RunModalLoop();
375 dlgMessage.DestroyWindow();
381 * Method for retrieving the former result of the message box from the registry.
383 int CMessageBoxDialog::GetFormerResult()
385 // Check whether the registry key was already generated.
386 if (m_strRegistryKey.IsEmpty())
388 // Create the registry key for this dialog.
389 m_strRegistryKey = GenerateRegistryKey();
392 // Try to read the former result of the message box from the registry.
393 return AfxGetApp()->GetProfileInt(
394 REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, (-1));
398 * Method for storing the former result of the message box to the registry.
400 int CMessageBoxDialog::SetFormerResult(int nResult)
402 int nOldResult = GetFormerResult();
403 // Try to write the former result of the message box to the registry.
404 AfxGetApp()->WriteProfileInt(
405 REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, nResult);
410 * Method for displaying the dialog.
412 * If the MB_DONT_DISPLAY_AGAIN or MB_DONT_ASK_AGAIN flag is set, this
413 * method will check, whether a former result for this dialog was stored
414 * in the registry. If yes, the former result will be returned without
415 * displaying the dialog. Otherwise the message box will be displayed in
418 INT_PTR CMessageBoxDialog::DoModal ( )
420 // Check whether the result may be retrieved from the registry.
421 if ( ( m_nStyle & MB_DONT_DISPLAY_AGAIN ) ||
422 ( m_nStyle & MB_DONT_ASK_AGAIN ) )
424 // Try to read the former result of the message box from the registry.
425 int nFormerResult = GetFormerResult();
427 // Check whether a result was retrieved.
428 if ( nFormerResult != (-1) )
430 // Return the former result without displaying the dialog.
431 return nFormerResult;
435 if (m_nStyle & MB_MODELESS) {
436 // Show the messsage box dialog asyncronously.
437 ModelessMesssageBoxParam *pParam = new ModelessMesssageBoxParam();
438 pParam->strMessage = m_strMessage.c_str();
439 pParam->nType = m_nStyle & ~MB_MODELESS;
440 AfxBeginThread(ModelessMesssageBoxThread, pParam);
444 // Call the parent method.
445 return __super::DoModal();
449 * Method for closing the dialog.
451 * If the MB_DONT_DISPLAY_AGAIN or MB_DONT_ASK_AGAIN flag is set, this
452 * method will check, one of the checkbox was marked to save the result in
453 * the registry. If yes, the result of this dialog will be stored in the
456 void CMessageBoxDialog::EndDialog ( int nResult )
458 // Create a variable for storing the state of the checkbox.
459 bool bDontDisplayAgain = false;
461 // Try to access the checkbox control.
462 CWnd* pCheckboxWnd = GetDlgItem(IDCHECKBOX);
464 // Check whether the control can be accessed.
465 if ( pCheckboxWnd != nullptr )
467 // Check whether the checkbox is checked.
469 ( ((CButton*)pCheckboxWnd)->GetCheck() == BST_CHECKED );
472 // Check whether the result may be stored in the registry.
473 if ( ( ( m_nStyle & MB_DONT_DISPLAY_AGAIN ) && bDontDisplayAgain ) ||
474 ( ( m_nStyle & MB_DONT_ASK_AGAIN ) && bDontDisplayAgain ) )
476 SetFormerResult(nResult);
479 // Call the parent method.
480 __super::EndDialog(nResult);
484 * Method for initializing the dialog.
486 * This method is used for initializing the dialog. It will create the
487 * content of the dialog, which means it will create all controls and will
488 * size the dialog to fit it's content.
490 BOOL CMessageBoxDialog::OnInitDialog ( )
492 // Call the parent method.
493 if ( !__super::OnInitDialog() )
495 // Return with an error.
499 // Set the title of the dialog.
500 SetWindowText(m_strTitle.c_str());
502 // Set the help ID of the dialog.
505 // Parse the style of the message box.
508 // Create the elements of the dialog.
509 m_tooltips.Create(this);
511 CreateMessageControl();
512 CreateCheckboxControl();
513 CreateButtonControls();
515 // Define the layout of the dialog.
518 // Check whether no sound should be generated.
519 if ( !( m_nStyle & MB_NO_SOUND ) )
522 MessageBeep(m_nStyle & MB_ICONMASK);
525 // Check whether the window should be system modal.
526 if ( m_nStyle & MB_SYSTEMMODAL )
528 // Modify the style of the window.
529 ModifyStyle(0, DS_SYSMODAL);
532 // Check whether to bring the window to the foreground.
533 if ( m_nStyle & MB_SETFOREGROUND )
535 // Bring the window to the foreground.
536 SetForegroundWindow();
539 // Check whether the window should be the topmost window.
540 if ( m_nStyle & MB_TOPMOST )
542 // Modify the style of the window.
543 ModifyStyleEx(0, WS_EX_TOPMOST);
546 // Check whether an escape button was defined.
547 if ( m_nEscapeButton == IDC_STATIC )
549 // Disable the close item from the system menu.
550 GetSystemMenu(FALSE)->EnableMenuItem(SC_CLOSE, MF_GRAYED);
553 // Check whether a timeout is set.
554 if ( m_nTimeoutSeconds > 0 )
556 // Check whether it's a disabled timeout.
557 if ( m_bTimeoutDisabled )
559 // Run through all created buttons.
560 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
562 // Try to retrieve a handle for the button.
563 CWnd* pButtonWnd = GetDlgItem(iter->nID);
565 ASSERT(pButtonWnd != nullptr);
567 // Check whether the handle was retrieved.
568 if ( pButtonWnd != nullptr )
570 // Disable the button.
571 pButtonWnd->EnableWindow(FALSE);
575 // Try to retrieve the handle of the checkbox.
576 CWnd* pCheckboxWnd = GetDlgItem(IDCHECKBOX);
578 // Check whether the checkbox handle was retrieved.
579 if ( pCheckboxWnd != nullptr )
581 // Disable the checkbox.
582 pCheckboxWnd->EnableWindow(FALSE);
587 m_nTimeoutTimer = SetTimer(MESSAGE_BOX_TIMER, 1000, nullptr);
590 // Check whether a default button was defined.
591 if ( m_nDefaultButton != IDC_STATIC )
593 // Set the focus to the default button.
594 GetDlgItem(m_nDefaultButton)->SetFocus();
596 // Set the default ID of the dialog.
597 SetDefID(m_nDefaultButton);
599 // Return FALSE to set the focus correctly.
603 // Everything seems to be done successfully.
608 * Method for handling command messages.
610 * This method will handle command messages, which are those messages, which
611 * are generated, when a user clicks a button of the dialog.
613 BOOL CMessageBoxDialog::OnCmdMsg ( UINT nID, int nCode, void* pExtra,
614 AFX_CMDHANDLERINFO* pHandlerInfo )
616 // Check whether it's the help button.
617 if ( ( nID == IDHELP ) && ( nCode == CN_COMMAND ) )
619 // Display the help for this message box.
622 // The message has been processed successfully.
626 // Check whether the ID of the control element is interesting for us.
627 if ( ( nID != IDC_STATIC ) && ( nID != IDCHECKBOX ) &&
628 ( nCode == CN_COMMAND ) )
630 // End the dialog with the given ID.
633 // The message has been processed successfully.
637 // Call the parent method.
638 return __super::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
642 * Method for handling messages before dispatching them.
644 * This message will handle message before they get dispatched the normal way
645 * and will therefore implement the additional behavior of this dialog.
647 BOOL CMessageBoxDialog::PreTranslateMessage ( MSG* pMsg )
649 if (pMsg->message == WM_LBUTTONDOWN ||
650 pMsg->message == WM_LBUTTONUP ||
651 pMsg->message == WM_MOUSEMOVE)
653 m_tooltips.RelayEvent(pMsg);
656 // Check whether it's a key message and whether it's not a disable timeout.
657 if ( pMsg->message == WM_KEYDOWN )
659 // Check whether a disabled timeout is running.
660 if ( m_bTimeoutDisabled && ( m_nTimeoutSeconds > 0 ) )
662 // Stop here and do nothing until the timeout is finished.
666 // Check whether it's the return key.
667 if ( pMsg->wParam == VK_RETURN )
669 // Try to retrieve the current focus.
670 CWnd* pFocusWnd = GetFocus();
672 // Check whether a handle was retrieved.
673 if ( pFocusWnd != nullptr )
675 // Try to determine the ID of the element.
676 int nID = pFocusWnd->GetDlgCtrlID();
678 // Run through the list of defined buttons.
679 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
681 // Check whether the ID is a button.
682 if ( iter->nID == static_cast<UINT>(nID) )
684 // Save this ID as the default ID.
685 m_nDefaultButton = nID;
687 // Break the loop to save time.
692 // End the dialog with the default command.
693 EndDialog(m_nDefaultButton);
695 // The message has been processed successfully.
700 // Check whether it's the escape key.
701 if ( ( pMsg->wParam == VK_ESCAPE ) || ( pMsg->wParam == VK_CANCEL ) )
703 // Check whether an escape button was defined.
704 if ( m_nEscapeButton != IDC_STATIC )
706 // End the dialog with this ID.
707 EndDialog(m_nEscapeButton);
710 // The message has been processed successfully.
715 // Call the parent method.
716 return __super::PreTranslateMessage(pMsg);
720 * Method for handling a timer event.
722 * When a timeout for the message box is set, this method will perform the
723 * update of the dialog controls every second.
725 void CMessageBoxDialog::OnTimer ( UINT_PTR nIDEvent )
727 // Check whether the event is interesting for us.
728 if ( nIDEvent == MESSAGE_BOX_TIMER )
730 // Decrease the remaining seconds.
733 // Check whether the timeout is finished.
734 if ( m_nTimeoutSeconds == 0 )
736 // Kill the timer for this event and reset the handle.
737 KillTimer(m_nTimeoutTimer);
739 // Check whether it has been a disabled timeout.
740 if ( m_bTimeoutDisabled )
742 // Run through all defined buttons.
743 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
745 // Try to retrieve a handle to access the button.
746 CWnd* pButtonWnd = GetDlgItem(iter->nID);
748 ASSERT(pButtonWnd != nullptr);
750 // Check whether a handle was retrieved.
751 if ( pButtonWnd != nullptr )
753 // Enable the button again.
754 pButtonWnd->EnableWindow(TRUE);
758 // Try to retrieve a handle for the checkbox.
759 CWnd* pCheckboxWnd = GetDlgItem(IDCHECKBOX);
761 // Check whether the checkbox was found.
762 if ( pCheckboxWnd != nullptr )
764 // Enable the checkbox.
765 pCheckboxWnd->EnableWindow(TRUE);
770 // End the dialog with the default button.
771 EndDialog(m_nDefaultButton);
775 // Run through the list of defined buttons.
776 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
778 // Check whether this button is the default button.
779 if ( iter->nID == static_cast<UINT>(m_nDefaultButton) )
781 // Try to load the text for the button.
782 String strButtonText = LoadResString(iter->nTitle);
783 // Check whether the timeout is finished.
784 if ( m_nTimeoutSeconds > 0 )
786 // Add the remaining seconds to the text of the button.
787 TCHAR szTimeoutSeconds[40];
788 wsprintf(szTimeoutSeconds, _T(" = %u"), m_nTimeoutSeconds);
789 strButtonText += szTimeoutSeconds;
791 // Set the text of the button.
792 SetDlgItemText(iter->nID, strButtonText.c_str());
797 // Call the parent method.
798 __super::OnTimer(nIDEvent);
801 BOOL CMessageBoxDialog::OnEraseBkgnd(CDC* pDC)
804 GetClientRect(&rect);
805 pDC->FillSolidRect(&rect, ::GetSysColor(COLOR_BTNFACE));
806 rect.bottom = rect.bottom - YDialogUnitToPixel(CY_BUTTON + CY_BORDER * 2);
807 pDC->FillSolidRect(&rect, ::GetSysColor(COLOR_WINDOW));
811 HBRUSH CMessageBoxDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
813 if (nCtlColor == CTLCOLOR_STATIC)
815 pDC->SetBkMode(OPAQUE);
816 pDC->SetBkColor(::GetSysColor(COLOR_WINDOW));
817 pDC->SetTextColor(m_clrMainInstructionFont);
818 return static_cast<HBRUSH>(GetSysColorBrush(COLOR_WINDOW));
820 return __super::OnCtlColor(pDC, pWnd, nCtlColor);
823 //////////////////////////////////////////////////////////////////////////////
824 // Other dialog handling methods.
827 * Method for handling window messages.
829 BOOL CMessageBoxDialog::OnWndMsg ( UINT message, WPARAM wParam, LPARAM lParam,
832 // Check whether to close the dialog.
833 if ( message == WM_CLOSE )
835 // Check whether a disabled timeout is running.
836 if ( m_bTimeoutDisabled && ( m_nTimeoutSeconds > 0 ) )
838 // Stop here and do nothing until the timeout is finished.
842 // Check whether an escape button is defined.
843 if ( m_nEscapeButton != IDC_STATIC )
845 // End the dialog with this command.
846 EndDialog(m_nEscapeButton);
849 // The message was handled successfully.
853 // Call the parent method.
854 return __super::OnWndMsg(message, wParam, lParam, pResult);
857 BEGIN_MESSAGE_MAP(CMessageBoxDialog, CDialog)
863 //////////////////////////////////////////////////////////////////////////////
867 * Method for generating a registry key.
869 * This method tries to create a registry key, which will be used for storing
870 * the result of the message box, if the MB_DONT_DISPLAY_AGAIN or the
871 * MB_DONT_ASK_AGAIN flag is set.
873 CString CMessageBoxDialog::GenerateRegistryKey ( )
875 // Create a string to store the registry key.
876 CString strRegistryKey = _T("");
878 // Check whether a help ID is given.
881 // Simply use the help ID, because we assume, it's unique.
882 strRegistryKey.Format(_T("%u"), m_nHelp);
886 // POSSIBLE BUG: The following algorithm for creating a checksum is
887 // very simple and may not ensure, the registry key is really unique.
888 // But for now it may be enough.
890 // Create a variable to store the checksum.
893 // Run through the message string.
894 for ( String::size_type i = 0; i < m_strMessage.length(); i++ )
896 // Get the char at the given position and add it to the checksum.
897 nChecksum += static_cast<int>(static_cast<int>(m_strMessage[i]) * i);
900 // Convert the checksum to a string.
901 strRegistryKey.Format(_T("%d"), nChecksum);
904 // Return the registry key.
905 return strRegistryKey;
909 * Method for adding a button to the list of buttons.
911 * This method adds a button to the list of buttons, which will be created in
912 * the dialog, but it will not create the button control itself.
914 void CMessageBoxDialog::AddButton ( UINT nID, UINT nTitle, bool bIsDefault /*= false*/,
915 bool bIsEscape /*= false*/ )
917 // Create a new structure to store the button information.
918 MSGBOXBTN bButton = { nID, nTitle };
920 // Add the button to the list of buttons.
921 m_aButtons.push_back(bButton);
923 // Check whether this button is the default button.
926 // Save the ID of the button as the ID of the default button.
927 m_nDefaultButton = nID;
930 // Check whether this button is the escape button.
933 // Save the ID of the button as the ID of the escape button.
934 m_nEscapeButton = nID;
939 * Method for converting a dialog unit x value to a pixel value.
941 inline int CMessageBoxDialog::XDialogUnitToPixel ( int x )
943 // Check whether the dimension of a dialog unit has already been determined.
944 if ( m_sDialogUnit.cx == 0 )
946 // Create a rect for mapping it to the dialog rect.
947 CRect rcDialog(0, 0, CX_DLGUNIT_BASE, CY_DLGUNIT_BASE);
949 // Map the rect to the dialog.
950 MapDialogRect(rcDialog);
953 m_sDialogUnit = rcDialog.Size();
956 // Return the converted value.
957 return ( x * m_sDialogUnit.cx / CX_DLGUNIT_BASE );
961 * Method for converting a dialog unit y value to a pixel value.
963 inline int CMessageBoxDialog::YDialogUnitToPixel ( int y )
965 // Check whether the dimension of a dialog unit has already been determined.
966 if ( m_sDialogUnit.cy == 0 )
968 // Create a rect for mapping it to the dialog rect.
969 CRect rcDialog(0, 0, CX_DLGUNIT_BASE, CY_DLGUNIT_BASE);
971 // Map the rect to the dialog.
972 MapDialogRect(rcDialog);
975 m_sDialogUnit = rcDialog.Size();
978 // Return the converted value.
979 return ( y * m_sDialogUnit.cy / CY_DLGUNIT_BASE );
983 * Method for parsing the given style.
985 * This method will parse the given style for the message box and will create
986 * the elements of the dialog box according to it. If you want to add more
987 * user defined styles, simply modify this method.
989 void CMessageBoxDialog::ParseStyle ( )
991 // Switch the style of the buttons.
992 switch ( m_nStyle & MB_TYPEMASK )
997 // Add two buttons: "Ok" and "Cancel".
998 AddButton(IDOK, IDS_MESSAGEBOX_OK, true);
999 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, false, true);
1003 case MB_ABORTRETRYIGNORE:
1005 // Add three buttons: "Abort", "Retry" and "Ignore".
1006 AddButton(IDABORT, IDS_MESSAGEBOX_ABORT, true);
1007 AddButton(IDRETRY, IDS_MESSAGEBOX_RETRY);
1008 AddButton(IDIGNORE, IDS_MESSAGEBOX_IGNORE);
1012 case MB_YESNOCANCEL:
1014 // Add three buttons: "Yes", "No" and "Cancel".
1015 AddButton(IDYES, IDS_MESSAGEBOX_YES, true);
1017 // Check whether to add a "Yes to all" button.
1018 if ( m_nStyle & MB_YES_TO_ALL )
1020 // Add the additional button.
1021 AddButton(IDYESTOALL, IDS_MESSAGEBOX_YES_TO_ALL);
1024 AddButton(IDNO, IDS_MESSAGEBOX_NO);
1026 // Check whether to add a "No to all" button.
1027 if ( m_nStyle & MB_NO_TO_ALL )
1029 // Add the additional button.
1030 AddButton(IDNOTOALL, IDS_MESSAGEBOX_NO_TO_ALL);
1033 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, false, true);
1039 // Add two buttons: "Yes" and "No".
1040 AddButton(IDYES, IDS_MESSAGEBOX_YES, true);
1042 // Check whether to add a "Yes to all" button.
1043 if ( m_nStyle & MB_YES_TO_ALL )
1045 // Add the additional button.
1046 AddButton(IDYESTOALL, IDS_MESSAGEBOX_YES_TO_ALL);
1049 AddButton(IDNO, IDS_MESSAGEBOX_NO);
1051 // Check whether to add a "No to all" button.
1052 if ( m_nStyle & MB_NO_TO_ALL )
1054 // Add the additional button.
1055 AddButton(IDNOTOALL, IDS_MESSAGEBOX_NO_TO_ALL);
1060 case MB_RETRYCANCEL:
1062 // Add two buttons: "Retry" and "Cancel".
1063 AddButton(IDRETRY, IDS_MESSAGEBOX_RETRY, true);
1064 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, false, true);
1068 case MB_CANCELTRYCONTINUE:
1070 // Add three buttons: "Cancel", "Try again" and "Continue".
1071 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, true, true);
1072 AddButton(IDTRYAGAIN, IDS_MESSAGEBOX_RETRY);
1073 AddButton(IDCONTINUE, IDS_MESSAGEBOX_CONTINUE);
1077 case MB_CONTINUEABORT:
1079 // Add two buttons: "Continue" and "Abort".
1080 AddButton(IDCONTINUE, IDS_MESSAGEBOX_CONTINUE, true);
1081 AddButton(IDABORT, IDS_MESSAGEBOX_ABORT);
1085 case MB_SKIPSKIPALLCANCEL:
1087 // Add three buttons: "Skip", "Skip all" and "Cancel".
1088 AddButton(IDSKIP, IDS_MESSAGEBOX_SKIP, true);
1089 AddButton(IDSKIPALL, IDS_MESSAGEBOX_SKIPALL);
1090 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, false, true);
1094 case MB_IGNOREIGNOREALLCANCEL:
1096 // Add three buttons: "Ignore", "Ignore all" and "Cancel".
1097 AddButton(IDIGNORE, IDS_MESSAGEBOX_IGNORE, true);
1098 AddButton(IDIGNOREALL, IDS_MESSAGEBOX_IGNOREALL);
1099 AddButton(IDCANCEL, IDS_MESSAGEBOX_CANCEL, false, true);
1106 // Add just one button: "Ok".
1107 AddButton(IDOK, IDS_MESSAGEBOX_OK, true, true);
1113 // Check whether to add a help button.
1114 if ( m_nStyle & MB_HELP )
1116 // Add the help button.
1117 AddButton(IDHELP, IDS_MESSAGEBOX_HELP);
1120 // Check whether a default button was defined.
1121 if ( m_nStyle & MB_DEFMASK )
1123 // Create a variable to store the index of the default button.
1124 vector<MSGBOXBTN>::size_type nDefaultIndex = 0;
1126 // Switch the default button.
1127 switch ( m_nStyle & MB_DEFMASK )
1132 // Set the index of the default button.
1139 // Set the index of the default button.
1146 // Set the index of the default button.
1153 // Set the index of the default button.
1160 // Set the index of the default button.
1167 // Set the index of the default button.
1174 // Check whether enough buttons are available.
1175 if ( m_aButtons.size() >= ( nDefaultIndex + 1 ) )
1177 // Set the new default button.
1178 m_nDefaultButton = m_aButtons[nDefaultIndex].nID;
1182 // Check whether an icon was specified.
1183 if ( ( m_nStyle & MB_ICONMASK ) && ( m_hIcon == nullptr ) )
1186 switch ( m_nStyle & MB_ICONMASK )
1189 case MB_ICONEXCLAMATION:
1191 // Load the icon with the exclamation mark.
1192 m_hIcon = AfxGetApp()->LoadStandardIcon(IDI_EXCLAMATION);
1198 // Load the icon with the error symbol.
1199 m_hIcon = AfxGetApp()->LoadStandardIcon(IDI_HAND);
1203 case MB_ICONQUESTION:
1205 // Load the icon with the question mark.
1206 m_hIcon = AfxGetApp()->LoadStandardIcon(IDI_QUESTION);
1210 case MB_ICONASTERISK:
1212 // Load the icon with the information symbol.
1213 m_hIcon = AfxGetApp()->LoadStandardIcon(IDI_ASTERISK);
1222 * Method for creating the icon control.
1224 * This method will check whether the handle for an icon was defined and if
1225 * yes it will create an control in the dialog to display that icon.
1227 void CMessageBoxDialog::CreateIconControl ( )
1229 // Check whether an icon was defined.
1230 if ( m_hIcon != nullptr )
1232 // Create a structure to read information about the icon.
1233 ICONINFO iiIconInfo;
1235 // Retrieve information about the icon.
1236 GetIconInfo(m_hIcon, &iiIconInfo);
1238 ASSERT(iiIconInfo.fIcon);
1240 // Create a handle to access the bitmap information of the icon.
1243 // Retrieve the bitmap information of the icon.
1244 GetObject((HGDIOBJ)iiIconInfo.hbmColor, sizeof(bmIcon), &bmIcon);
1246 // Save the size of the icon.
1247 m_sIcon.cx = bmIcon.bmWidth;
1248 m_sIcon.cy = bmIcon.bmHeight;
1250 // Create a dummy rect for the icon control.
1253 // Create the control for the icon.
1254 m_stcIcon.Create(nullptr, WS_CHILD | WS_VISIBLE | WS_DISABLED | SS_ICON,
1255 rcDummy, this, (UINT)IDC_STATIC);
1257 // Set the icon of the control.
1258 m_stcIcon.SetIcon(m_hIcon);
1263 * Method for creating the text control.
1265 * This method create the control displaying the text of the message for the
1266 * message box. It will also try to determine the size required for the
1269 void CMessageBoxDialog::CreateMessageControl ( )
1271 ASSERT(!m_strMessage.empty());
1273 // Create a DC for accessing the display driver.
1275 dcDisplay.CreateDC(_T("DISPLAY"), nullptr, nullptr, nullptr);
1277 // Select the new font and store the old one.
1278 CFont* pOldFont = dcDisplay.SelectObject(&m_fontMainInstruction);
1280 // Define the maximum width of the message.
1281 int nMaxWidth = ( GetSystemMetrics(SM_CXSCREEN) / 2 ) + 100;
1283 // Check whether an icon is displayed.
1284 if ( m_hIcon != nullptr )
1286 // Decrease the maximum width.
1287 nMaxWidth -= m_sIcon.cx + 2 * XDialogUnitToPixel(CX_BORDER);
1290 // Create a rect with the maximum width.
1291 CRect rcMessage(0, 0, nMaxWidth, nMaxWidth);
1293 // Draw the text and retrieve the size of the text.
1294 dcDisplay.DrawText(m_strMessage.c_str(), rcMessage, DT_LEFT | DT_NOPREFIX |
1295 DT_WORDBREAK | DT_EXPANDTABS | DT_CALCRECT);
1297 // Save the size required for the message.
1298 m_sMessage = rcMessage.Size();
1300 // Select the old font again.
1301 dcDisplay.SelectObject(pOldFont);
1303 // Create a dummy rect for the control.
1306 // Create a variable with the style of the control.
1307 DWORD dwStyle = WS_CHILD | WS_VISIBLE;
1309 // Check whether the text should be right aligned.
1310 if ( m_nStyle & MB_RIGHT )
1312 // Align the text to the right.
1313 dwStyle |= SS_RIGHT;
1317 // Align the text to the left.
1321 // Create the static control for the message.
1322 String strMessage = m_strMessage;
1323 strutils::replace(strMessage, _T("&"), _T("&&"));
1324 m_stcMessage.Create(strMessage.c_str(), dwStyle, rcDummy, this,
1327 // Check whether the text will be read from right to left.
1328 if ( m_nStyle & MB_RTLREADING )
1330 // Change the extended style of the control.
1331 m_stcMessage.ModifyStyleEx(0, WS_EX_RTLREADING);
1334 // Set the font of the dialog.
1335 m_stcMessage.SetFont(&m_fontMainInstruction);
1339 * Method for creating the checkbox control.
1341 * If the user either specified the MB_DONT_DISPLAY_AGAIN or
1342 * MB_DONT_ASK_AGAIN flag, this method will create a checkbox in the dialog
1343 * for enabling the user to disable this dialog in the future.
1345 void CMessageBoxDialog::CreateCheckboxControl ( )
1347 // Check whether a checkbox is required.
1348 if ( ( m_nStyle & MB_DONT_DISPLAY_AGAIN ) ||
1349 ( m_nStyle & MB_DONT_ASK_AGAIN ) )
1351 // Create a variable for storing the title of the checkbox.
1352 String strCheckboxTitle;
1354 // Check which style is used.
1355 if ( m_nStyle & MB_DONT_DISPLAY_AGAIN )
1357 // Load the string for the checkbox.
1358 strCheckboxTitle = LoadResString(IDS_MESSAGEBOX_DONT_DISPLAY_AGAIN);
1362 // Check which style is used.
1363 if ( m_nStyle & MB_DONT_ASK_AGAIN )
1365 // Load the string for the checkbox.
1366 strCheckboxTitle = LoadResString(IDS_MESSAGEBOX_DONT_ASK_AGAIN);
1370 ASSERT(!strCheckboxTitle.empty());
1372 // Create a handle to access the DC of the dialog.
1375 // Retrieve the font for this dialog and select it.
1376 CFont* pWndFont = &m_font;
1377 CFont* pOldFont = dc.SelectObject(pWndFont);
1379 // Retrieve the size of the text.
1380 m_sCheckbox = dc.GetTextExtent(strCheckboxTitle.c_str(), static_cast<int>(strCheckboxTitle.length()));
1382 // Add the additional value to the width of the checkbox.
1383 m_sCheckbox.cx += XDialogUnitToPixel(CX_CHECKBOX_ADDON);
1385 // Select the old font again.
1386 dc.SelectObject(pOldFont);
1388 // Create a dummy rect for the checkbox.
1391 // Create a handle for the checkbox.
1392 CButton btnCheckbox;
1394 // Create the checkbox.
1395 btnCheckbox.Create(strCheckboxTitle.c_str(), WS_CHILD | WS_VISIBLE |
1396 WS_TABSTOP | BS_AUTOCHECKBOX, rcDummy, this, IDCHECKBOX);
1398 // Check whether the checkbox should be marked checked at startup.
1399 if ( m_nStyle & MB_DEFAULT_CHECKED )
1401 // Mark the checkbox.
1402 btnCheckbox.SetCheck(BST_CHECKED);
1405 // Set the font of the control.
1406 btnCheckbox.SetFont(pWndFont);
1408 // Add a tooltip to the checkbox
1409 m_tooltips.AddTool(&btnCheckbox, LoadResString(IDS_MESSAGEBOX_CHECKBOX_TOOLTIP).c_str());
1411 // Remove the subclassing again.
1412 btnCheckbox.UnsubclassWindow();
1417 * Method for creating the button controls.
1419 * According to the list of buttons, which should be displayed in this
1420 * message box, this method will create them and add them to the dialog.
1422 void CMessageBoxDialog::CreateButtonControls ( )
1424 // Initialize the control with the size of the button.
1425 m_sButton = CSize(XDialogUnitToPixel(CX_BUTTON),
1426 YDialogUnitToPixel(CY_BUTTON));
1428 // Create a handle to access the DC of the dialog.
1431 // Retrieve the font for this dialog and select it.
1432 CFont* pWndFont = &m_font;
1433 CFont* pOldFont = dc.SelectObject(pWndFont);
1435 // Create a dummy rect.
1438 // Run through all buttons defined in the list of the buttons.
1439 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
1441 // Create a string and load the title of the button.
1442 String strButtonText = LoadResString(iter->nTitle);
1443 // Check whether there's a timeout set.
1444 if ( m_nTimeoutSeconds > 0 )
1446 // Add the remaining seconds to the text of the button.
1447 TCHAR szTimeoutSeconds[40];
1448 wsprintf(szTimeoutSeconds, _T(" = %u"), m_nTimeoutSeconds);
1449 strButtonText += szTimeoutSeconds;
1452 // Retrieve the size of the text.
1453 CSize sButtonText = dc.GetTextExtent(strButtonText.c_str(), static_cast<int>(strButtonText.length()));
1455 // Resize the button.
1456 m_sButton.cx = max(m_sButton.cx, sButtonText.cx);
1457 m_sButton.cy = max(m_sButton.cy, sButtonText.cy);
1459 // Create a new handle for creating a button control.
1462 // Create the button.
1463 btnControl.Create(strButtonText.c_str(), WS_CHILD | WS_VISIBLE | WS_TABSTOP,
1464 rcDummy, this, iter->nID);
1466 // Set the font of the control.
1467 btnControl.SetFont(pWndFont);
1469 // Remove the subclassing again.
1470 btnControl.UnsubclassWindow();
1473 // Add margins to the button size.
1474 m_sButton.cx += 2 * XDialogUnitToPixel(CX_BUTTON_BORDER);
1475 m_sButton.cy += 2 * XDialogUnitToPixel(CY_BUTTON_BORDER);
1477 // Select the old font again.
1478 dc.SelectObject(pOldFont);
1482 * Method for defining the layout of the dialog.
1484 * This method will define the actual layout of the dialog. This layout is
1485 * based on the created controls for the dialog.
1487 void CMessageBoxDialog::DefineLayout ( )
1489 // Create a variable for storing the size of the dialog.
1490 CSize sClient = CSize(4 * XDialogUnitToPixel(CX_BORDER),
1491 3 * YDialogUnitToPixel(CY_BORDER));
1493 // Create a variable to store the left position for a control element.
1494 int nXPosition = XDialogUnitToPixel(CX_BORDER) * 2;
1495 int nYPosition = YDialogUnitToPixel(CY_BORDER) * 2;
1497 // Check whether an icon is defined.
1498 if ( m_hIcon != nullptr )
1500 // Move the icon control.
1501 m_stcIcon.MoveWindow(XDialogUnitToPixel(CX_BORDER) * 2,
1502 YDialogUnitToPixel(CY_BORDER) * 2, m_sIcon.cx, m_sIcon.cy);
1504 // Add the size of the icon to the size of the dialog.
1505 sClient.cx += m_sIcon.cx + XDialogUnitToPixel(CX_BORDER);
1506 sClient.cy += m_sIcon.cy + YDialogUnitToPixel(CY_BORDER);
1508 // Increase the x position for following control elements.
1509 nXPosition += m_sIcon.cx + XDialogUnitToPixel(CX_BORDER);
1512 // Change the size of the dialog according to the size of the message.
1513 sClient.cx += m_sMessage.cx + XDialogUnitToPixel(CX_BORDER);
1514 sClient.cy = max(sClient.cy, m_sMessage.cy + 2 *
1515 YDialogUnitToPixel(CY_BORDER) + YDialogUnitToPixel(CY_BORDER / 2));
1517 // Set the position of the message text.
1518 if (m_sMessage.cy < m_sIcon.cy)
1519 nYPosition += (m_sIcon.cy - m_sMessage.cy) / 2;
1520 m_stcMessage.MoveWindow(nXPosition, nYPosition, m_sMessage.cx,
1523 // Define the new y position.
1524 nYPosition += m_sMessage.cy + YDialogUnitToPixel(CY_BORDER) +
1525 YDialogUnitToPixel(CY_BORDER / 2);
1527 // Check whether an checkbox is defined.
1528 if ( ( m_nStyle & MB_DONT_DISPLAY_AGAIN ) ||
1529 ( m_nStyle & MB_DONT_ASK_AGAIN ) )
1531 // Try to determine the control element for the checkbox.
1532 CWnd* pCheckboxWnd = GetDlgItem(IDCHECKBOX);
1534 ASSERT(pCheckboxWnd != nullptr);
1536 // Check whether the control was retrieved.
1537 if ( pCheckboxWnd != nullptr )
1539 // Move the checkbox window.
1540 pCheckboxWnd->MoveWindow(nXPosition, nYPosition, m_sCheckbox.cx,
1543 // Resize the dialog if necessary.
1544 sClient.cx = max(sClient.cx, nXPosition + m_sCheckbox.cx +
1545 XDialogUnitToPixel(CX_BORDER));
1546 sClient.cy = max(sClient.cy, nYPosition + m_sCheckbox.cy +
1547 YDialogUnitToPixel(CY_BORDER));
1551 // Calculate the width of the buttons.
1552 int cxButtons = static_cast<int>(
1553 ( m_aButtons.size() - 1 ) * XDialogUnitToPixel(CX_BUTTON_SPACE) +
1554 m_aButtons.size() * m_sButton.cx);
1555 int cyButtons = m_sButton.cy;
1557 // Add the size of the buttons to the dialog.
1558 sClient.cx = max(sClient.cx, 2 * XDialogUnitToPixel(CX_BORDER) + cxButtons);
1559 sClient.cy += cyButtons + YDialogUnitToPixel(CY_BORDER) * 2;
1561 // Calculate the start y position for the buttons.
1562 int nXButtonPosition = ( sClient.cx - cxButtons ) / 2;
1563 int nYButtonPosition = sClient.cy - YDialogUnitToPixel(CY_BORDER) -
1566 // Check whether the buttons should be right aligned.
1567 if ( m_nStyle & MB_RIGHT_ALIGN )
1569 // Right align the buttons.
1570 nXButtonPosition = sClient.cx - cxButtons -
1571 XDialogUnitToPixel(CX_BORDER);
1574 // Run through all buttons.
1575 for (vector<MSGBOXBTN>::iterator iter = m_aButtons.begin(); iter != m_aButtons.end(); ++iter)
1577 // Try to retrieve the handle to access the button.
1578 CWnd* pButton = GetDlgItem(iter->nID);
1580 ASSERT(pButton != nullptr);
1582 // Check whether the handle was retrieved successfully.
1583 if ( pButton != nullptr )
1586 pButton->MoveWindow(nXButtonPosition, nYButtonPosition,
1587 m_sButton.cx, m_sButton.cy);
1589 // Set the new x position of the next button.
1590 nXButtonPosition += m_sButton.cx +
1591 XDialogUnitToPixel(CX_BUTTON_SPACE);
1595 // Set the dimensions of the dialog.
1596 CRect rcClient(0, 0, sClient.cx, sClient.cy);
1598 // Calculate the window rect.
1599 CalcWindowRect(rcClient);
1602 MoveWindow(rcClient);
1604 // Center the window.