Extended Message Box (CTcxMsgBox)

Environment: Windows 95, Visual C++ 6 SP2

Here I present a extended message box class for MFC applications (CTcxMsgBox). It's an attempt to improve a couple of limitations of the regular API provided message box.

First, thanks to my friend Christian M. Garbin, who suggested I should prefix my classes with my initials. Uh, I really haven't thought that.

With a CTcxMsgBox, you can:

- Customize the buttons. You can choose as many buttons as you need, and you can give them whatever command ID and title you want. Therefore, you're not restrained to the old Abort/Retry/Ignore,OK, OK/Cancel, Retry/Cancel, Yes/No, Yes/No/Cancel schemas.
- Select the default and the escape button.
- Select any icon to decorate the message.
- Display messages in Rich Text Format. Therefore, you can format your important messages to really grab user's attention to what's important.

For example, the message box in the snapshot at the top of this page was produced by the following code:

static const LPCTSTR _pMsg =
 "{\\rtf1\\ansi\\deff0\\deftab720{\\fonttbl{\\f0\\fswiss MS "
 "Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\froman "
 "Times New Roman;}{\\f3\\froman Times New Roman;}{\\f4\\fswiss"
 "\\fprq2 Arial;}}"

 "{\\colortbl\\red0\\green0\\blue0;}"

 "\\deflang1046\\pard\\plain\\f4\\fs20 What about a Message Box "
 "with the following features?"

 "\\par \\pard\\li360\\fi-360{\\*\\pn\\pnlvlblt\\pnf1\\pnindent"
 "360{\\pntxtb\\'b7}}\\plain\\f4\\fs20\\b {\\pntext\\f1\\'b7\\tab"
 "}R\\plain\\f4\\fs20\\i ich \\plain\\f4\\fs20\\b T\\plain\\f4\\"
 "fs20\\i ext \\plain\\f4\\fs20\\b F\\plain\\f4\\fs20\\i ormat\\"
 "plain\\f4\\fs20  support;\\plain\\f2\\fs20 "

 "\\par \\plain\\f4\\fs20 {\\pntext\\f1\\'b7\\tab}Automatic "
 "layout calculation for best size;\\plain\\f2\\fs20 "

 "\\par \\plain\\f4\\fs20 {\\pntext\\f1\\'b7\\tab}Customizeable "
 "\\plain\\f4\\fs20\\b Buttons\\plain\\f4\\fs20 ;\\plain\\f2\\fs20 "

 "\\par \\plain\\f4\\fs20 {\\pntext\\f1\\'b7\\tab}Customizeable "
 "\\plain\\f4\\fs20\\b Icon\\plain\\f4\\fs20 ;\\plain\\f2\\fs20 "

 "\\par \\plain\\f4\\fs20 {\\pntext\\f1\\'b7\\tab}Customizeable "
 "\\plain\\f4\\fs20\\b Title\\plain\\f4\\fs20 ;\\plain\\f2\\fs20 "
 
 "\\par \\pard\\plain\\f4\\fs20 "
 
 "\\par Wouldn't that be really \\plain\\f4\\fs28\\b\\i \"cool\" "
 "\\plain\\f4\\fs20 ?\\plain\\f2\\fs20 "
 
 "\\par }";

CTcxMsgBox msg( this );

msg.SetTitle( IDS_TITLE3 );
msg.SetRtf();
msg.SetMsg( _pMsg );
msg.SetIcon( IDI_THUMB );

msg.AddButton( IDC_YEAH, TRUE, FALSE );
msg.AddButton( IDC_YES, FALSE, FALSE );
msg.AddButton( IDC_MAYBE, FALSE, FALSE );

msg.DoModal();
To create _pMsg string, I simply edited a RTF file with Microsoft's Wordpad, and opened it as plain text file in VC++. After that, I copied it to that C++ code, not forgetting to replace all the backslashes for double-backslashes (C/C++ syntax for constant string).

Well, I could have used Microsoft's Word instead of Wordpad. But, as far as I could see, Word puts a lot of default "framing" information in the file, even an empty file, that just makes it bigger, but have no effect over the visual aspect of the RFT data. Wordpad, for it's turn, gives us smaller data for the same result.

Another aspect of using RTF is, because of all that format information, even not so long messages are too big to fit in a single string slot of the resource's string table. Those string slots are limited to about 512 UNICODE characters.

If the message RTF data is bigger, one solution for this problem is to split the message in different parts and glue them all together in a CString object, before calling CTcxMsgBox.

Other solution is to use a CTcxMsgBox's mechanism that allows you saving messages as custom resources. Custom resources are basically binary data that you save with the resouces. Just create a custom resource type, and one resource of this type for each message. Then, just "paste" the message RTF data on the resource, and save it as binary. The Demo Application uses this mechanism.

Another feature that makes life easier are the FormatMsgXXX member functions. They're similar to CString::Format, so you don't have to create a CString and format it outside the message box. It does it for you.


Member functions

CTcxMsgBox( CWnd* pParentWnd = NULL )

Constructs a CTcxMsgBox object.

virtual ~CTcxMsgBox( void )

Destroys the object.

int DoModal( void )

Like CDialog::DoModal, this function displays the message box in modal mode (the only one allowed). The function waits in the modal loop until the user presses a buttons or dismisses the window by either closing it or pressing the esc key. Then, the function returns the code of the command that was used to dismiss the message box.

void AddButton( UINT uIDC, BOOL bIsDefault, BOOL bIsEscape, LPCTSTR pszText )
void AddButton( UINT uIDC, BOOL bIsDefault, BOOL bIsEscape, UINT uIdText = (UINT)-1 )

Adds a custom button to the message box. Buttons must be added in the sequence they will appear from left to right. Parameter uIDC is the button's command ID (e.g. IDOK). Parameter bIsDefault sets this button as the default button, and overrides any previous added button that used this style. Parameter bIsEscape sets this buttons as the escape button. Therefore, this button's command ID will be returned if the user either closes the window or presses the esc key. If no button is set as the escape button, the message box won't have the title's bar close button, neither will it be dismissed by the esc key. Parameter pszText points to a string to title the button. Parameter uIdText identifies a string, in the resource's string table, to title the button.

void SetTitle( LPCTSTR pszTitle )
void SetTitle( UINT uIdTitle )

Set the message box title. The default title, if this function is not used, is the application name (AfxGetApp()->m_pszAppName). The parameters are obvious.

void SetRtf( BOOL bRtf = TRUE )

Sets the message box to work in RTF mode. The default mode after construction is "RTF OFF". If the RTF is inactive, the object will work with messages as plain texts. The parameter is obvious.

BOOL SetMsg( UINT uMsgId )
BOOL SetMsg( LPCTSTR pszMsg )

Set the message to be displayed. The parameters are obvious.

BOOL SetMsgEx( LPCTSTR pszMsgResId, LPCTSTR pszMsgResType )

Loads the message from a custom type resource. Parameter pszMsgResId is the resource ID. If it's a UINT resource ID, use the MAKEINTRESOURCE to convert it to a LPCTSTR. Parameter pszMsgResType is the resource type. The Demo Application has several samples of using this mechanism.

BOOL FormatMsg( LPCTSTR pszFmt, ... )
BOOL FormatMsg( UINT uFmtStrId, ... )

Format the message to be displayed. See CString::Format for more information.

BOOL FormatMsgEx( LPCTSTR pszMsgResId, LPCTSTR pszMsgResType, ... )

Same as above. The only difference is that the formatting string is loaded from a custom resource.

BOOL FormatMsgV( LPCTSTR pszFmt, va_list marker )

See C/C++ runtime library's vsprintf for details of this kind of construction.

void SetIcon( HICON hIcon )
void SetIcon( UINT uIcon )

Sets the icon to be displayed. If no icon is set there'll be no icon in the message box. Well, that's pretty obvious. It's just to let you know that I don't put any undesired icon in that place :).

void SetStandardIcon( LPCTSTR pszIconName )

Sets the icon to be displayed. The icon is one of the standard icons (IDI_APPLICATION, IDI_HAND, IDI_QUESTION, IDI_EXCLAMATION, IDI_ASTERISK). For more details, see CWinApp::LoadStandardIcon.

void SetMetric( int iMetric, int xy )
int GetMetric( int iMetric )

These functions are intended to advance use only. You can change some metrics of the message box, like borders, gaps, etc. Parameter iMetric is the metric index. See the header file for the metric indexes enumeration.


Final notes

Remember that message boxes are not a solution, but an anomaly of the UI universe. A cool UI should use as few message boxes as possible. However, sometimes it's just that we don't have the time or resources to design that cute ultimate smooth UI, and we finish using message boxes now and then.

One good thing about the CTcxMsgBox is that you can make your messages to really look different, so improving the chances you'll grab user attention to what's really important or catastrophic. Therefore, the risks a user will mistake an important message, thus giving it a wrong order, by other he/she always gets in her/his way are lower.

For example, look at the following snapshots. The first is a regular VC++ message box. The second is a "replacement" with CTcxMsgBox. Note how it really highlights the important information. If the user never saw this message before, he/she would stop and pay attention to it. Besides, due to the visual cues, experienced users can extract the important information quicker.

I hope you really enjoy this class. Even better, I hope you make good use of it - let me know by then. And sorry for my not so wonderful English. This is not my first language, and besides I can write good enough English, I generally demands some energy to do that. But that would mean I would never have the time to write a good documentation, and to deploy the code. Therefore, I choose to be a little bit loose with the writting, but to give you the good code anyway.

Files to include

The only files you need to include in you project are:

  • TcxMsgBox.h - the header file
  • TcxMsgBox.cpp - the code file
These files are part of the Demo Application source code.

Downloads

Download demo application - 70 Kb
Download source code - 28 Kb


Comments

  • RTF message on CButton...

    Posted by Legacy on 12/13/2001 12:00am

    Originally posted by: Sas

    Excellent work. How about displaying RTF message on CButton? If any bod has idea, please give me some hints.

    Reply
  • Problem in Win XP

    Posted by Legacy on 11/19/2001 12:00am

    Originally posted by: Jason Yang

    If use AddButton to add any button in this class, the message box will not show up.

    Add a extra check in OnCmdMsg function to check the nCode == BN_CLICKED can fix it.

    Reply
  • How about the "MessageBox with Color Backgound" ?

    Posted by Legacy on 10/10/2001 12:00am

    Originally posted by: sungjin chung

    How do you think about the "MessageBox with Color Backgound Color " ?


    like using "OnCTLColor" function.

    Reply
  • Updated Version with Requested Features

    Posted by Legacy on 07/13/2001 12:00am

    Originally posted by: Tim Sylvester

    A timeout can now be specified by calling SetTimeout, specifying a timeout in milliseconds and a button ID. After the time has expired, the button with the specified ID will be pressed, just as if the user had pressed it. The TimedOut function can be called to determine whether the dialog closed by user action or by the timeout.

    I solved the problem of disabling selection by simply denying the text edit control the input focus. Just call EnableSelection(FALSE). (Selection is enabled by default)

    The new code is available at http://www.jepptech.com/TcxMsgBox.zip

    Reply
  • Let's make TcxMsgBox appears like a standard MessageBox

    Posted by Legacy on 06/28/2001 12:00am

    Originally posted by: Dmitry Sidorenko

    This piece of work is pretty nice, but is having one minor problem, the text in RichTextCtrl can be selected and thus is looking somehow ugly. To prevent selection, just add
    WS_DISABLED style in CTcxMsgBox::CreateRtfCtrl() function
    in call to m_edCtrl.Create(...).

    Don't give a chance to selections.

    Reply
  • Nicely done. Fine work! -nt

    Posted by Legacy on 02/16/2000 12:00am

    Originally posted by: Dinky

    nt = no text.

    Reply
  • Very nice - timer feature?

    Posted by Legacy on 02/10/2000 12:00am

    Originally posted by: nik

    Nice. Can you also make it a timed msg box, so that it can automatically disappear after the specified time.

    Thx again.

    Reply
  • Cool

    Posted by Legacy on 02/09/2000 12:00am

    Originally posted by: Hanks Lee

    This class is cool!!!!!!!!!!!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • As more and more organizations migrate to the cloud, many are faced with hidden costs emerging from unexpected places. Two non-obvious and non-trivial factors can drive up costs. First are separate charges for everything from server memory to intrusion detection. Second are the high personnel costs for early-generation, manually operated clouds. These costs can rack up quickly, creating total cost of ownership (TCO) surprises. Keeping TCO low in the cloud is essentially a matter of management strategy. IT …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds