Introduction
I recently needed to add a message to a list when some items which would normally be shown in the list were excluded due to access permissions. I wanted a message bar which I could easily add to the control, similar in appearance to the message bar seen in most web browsers when pop-ups and other content are blocked.
How I did it
I wanted to show the message at the top of a listview control, but I didn't want to have to resize the control to fit something above the control as a sibling, so I decided to add it to the non-client area above the main client area, by intercepting the WM_NCCALCSIZE
message in the control and leaving enough space to draw a message. I also wanted to provide the user with a way of dismissing the message, so I needed to respond to WM_NCLBUTTONDOWN
etc. messages within the message bar area.
Not wanting to restrict myself to only being able to use the message bar in a listview control, I looked at how I could make it generic enough to be easily added to any control. My first attempt was to create a class from which I would co-inherit some MFC classes, thereby adding support for a message bar to any of my control classes. The main downside of this was that I would need a class derived from the standard MFC class for every control that I wanted to give the message bar to. I, therefore, wanted a method which allowed me to simply add the functionality to any existing control.
Adding functionality to a control by sub-classing is simple, but when the control is already sub-classed, it is not possible to subclass the control a second time. Looking into this brought me to an article by Ralph Varjabedian on Double Subclassing, which allows a previously subclassed control to be subclassed again, providing me with exactly what I needed.
Once the control has been (double-)subclassed in order to provide the message bar functionality, the message bar code is then able to intercept the window's messages, which allows it to modify the control's non-client area to provide a space for the message bar. Also, the message bar can intercept mouse messages so that the program can respond to clicks on the message bar to show a menu or perform any other action.
Some controls make their own use of the non-client area, which can interfere with the message bar's calculated area. The most common example is the list-view control, which uses the non-client area to place the column header. We are fortunate though that the list control sends a message when calculating the header's size and position, and so the message bar intercepts this message to adjust the area used by the header control, thus allowing it to keep its own space.
How to use it
Using the CCtrlMessageBar
class is simple. Follow the steps below to add one to an existing project:
- After putting the source files (CtrlMessageBar.cpp and CtrlMessageBar.h) into the directory you wish to use them from, add the files to your Visual Studio project.
- Make sure you have a control to which you wish to add the message bar. I'll assume this is a list control, with a variable name of
m_list
. - Add a
CCtrlMessageBar
variable to your dialog class. I'll assume this is called m_barList
. - Add a handler for
WM_INITDIALOG
in your dialog class if you don't already have one, and add the following code to it to add a message bar and set its text:
m_barList.Attach(m_list);
m_barlist.SetText(_T("This is my message bar text"))
- You can set an image for the bar by attaching an image list and setting the image index to use:
CImageList imlMessageBar;
...
m_barList.SetImageList(imlMessageBar);
m_barList.SetImage(3);
That's all you need to do to get started. See the features or member function documentation below, or the demo application, for more advanced options.
Features
Some of the features included in the control, which you may find useful, are as follows:
Bars may have images
As in the example above, you may optionally set an image-list for the message bar, and then specify an image in that image-list to be displayed at the top left of the bar.
Text may be set to wrap
Text that is too long to fit in the bar's rectangle may be set to wrap, by calling the SetWrapText
method, thus increasing the height of the bar to accommodate the text. Without wrapping enabled, the text is truncated with an ellipsis.
Bar text is displayed in a tool-tip if truncated
If text is too long to fit in the message bar, and you have not set wrapping, then the text is right-truncated with an ellipsis (...), in which case hovering the mouse over the bar will display the entire text in a tool-tip. This can be used where text is likely to be too long to fit, but you do not want the height of the bar to be increased.
The bar background and text may be set to any colour
By default, the bar uses the system tool-tip background colour for its background, and the system tool-tip text colour for the text. These, along with the highlight colours (see below), may be overridden by specifying new colours to the SetColours
method. Specifying any colour as CLR_DEFAULT
causes it to be used as the default colour.
Bars may be set to 'light up' on mouse-over
You can make the bar 'light up' when the user moves their mouse over it by calling the SetHighlightOnMouseOver
method. The highlight background and text colours default to the system highlight colours, but may be set by calling the SetColours
method (see above).
The bar can be set to resize its parent to make room
Normally, attaching a bar to a control will resize the control's client area to make room for the bar, while leaving the control's window size as it was. While this is probably ideal for most controls, there are times when the opposite is required, for instance, adding a bar to the top of a dialog. By calling the SetResize
method with TRUE
, the bar will resize the window to enable the bar to fit while leaving the size of the window's client area as it was.
Custom menus may be invoked
By default, clicking or right-clicking on the message bar will display a menu offering the user one option, which is to hide the bar. By specifying a callback function, you may intercept this behaviour and supply your own menu, or carry out any other action, or allow the default behaviour. An example of such a callback function is included in the demo application, in the CMBEdit
class which subclasses the edit control (note though that it is not necessary to sub-class a control in order to provide a callback function).
Reference
Functions
The public member functions of the CCtrlMessageBar
class are as follows:
CCtrlMessageBar();
Standard empty constructor.
virtual ~CCtrlMessageBar();
Standard virtual destructor.
BOOL Attach(CWnd* pCtrl);
Used to attach the message bar to the control specified by pCtrl
. Returns TRUE
is successful, else FALSE
.
BOOL Attach(CWnd& ctrl)
Used to attach the message bar to the control specified by ctrl
. Returns TRUE
is successful, else FALSE
.
BOOL Attach(CWnd* pDlg, UINT nID);
Used to attach the message bar to the control specified by nID
on the dialog specified by pDlg
. Returns TRUE
is successful, else FALSE
.
CWnd* Detach();
Detaches the message bar from the control. Returns the control to which the message had been attached.
CWnd* GetCtrl() const
Returns the controls to which the message bar is attached.
BOOL IsAttached() const
Returns whether the message bar is currently attached to a control.
void SetImageList(CImageList* piml);
Sets the image list containing the images to be used by the message bar.
virtual void SetText(LPCTSTR lpszText, UINT nImage = -1, BOOL bShow = TRUE);
Sets the current text, and optionally the image index, for the message bar. If nImage
is -1, then no image is displayed. The bShow
parameter determines whether the message bar is shown immediately.
virtual CString GetText() const;
Returns the current message bar text.
virtual void Show(BOOL bShow = TRUE);
Shows or hides the message bar.
virtual BOOL IsShown() const;
Returns whether the message bar is currently shown.
void SetImage(UINT nImage, BOOL bShow = TRUE);
Specifies the index of the image in the image list to display on the message bar.
UINT GetImage() const;
Returns the index of the image currently shown on the message bar.
CRect GetRect(BOOL bDrawing = FALSE) const;
Returns the size and position of the message bar in window co-ordinates.
CRect GetCloseButtonRect(CRect& rc) const;
Returns the size and position of the message bar's close button.
UINT GetMessageHeight() const;
Returns the height, in pixels, of the message bar.
void SetCloseButton(BOOL bShow = TRUE);
Sets whether to show a close button on the message bar.
BOOL GetCloseButton() const
Returns whether a close button is shown on the message bar.
void SetWrapText(BOOL bWrap = TRUE)
Sets whether the message bar text should be wrapped, if too long, to fit in the width of the bar. If text is not wrapped, and it is too long, then text is truncated with an ellipsis (...).
BOOL GetWrapText() const
Returns whether text will wrap if too long to fit in the width of the bar.
void SetHighlightOnMouseOver(BOOL bHighlight = TRUE)
Sets whether the bar should 'light up' when the user's mouse moves over it.
BOOL GetHighlightOnMouseOver() const
Returns whether the control is set to 'light up' when the user's mouse moves over it.
void SetColours(COLORREF crBackgroundNormal = CLR_DEFAULT, COLORREF crTextNormal = CLR_DEFAULT, COLORREF crBackgroundHilite = CLR_DEFAULT, COLORREF crTextHilite = CLR_DEFAULT);
Sets colours to use for the various aspects of the message bar. Specifying CLR_DEFAULT
for any of the colours causes the bar to use its own defaults.
void GetColours(COLORREF& crBackgroundNormal, COLORREF& crTextNormal, COLORREF& crBackgroundHilite, COLORREF& crTextHilite) const;
Used to get the current colours used for the various aspects of the message bar.
void SetResize(BOOL bResize = TRUE)
Specifies whether the window hosting the message bar should be resized to accommodate the message bar. The normal behaviour is to reduce the window's client area to make room for the message bar while leaving the window's size and position untouched. This option is useful when adding a message bar to a dialog or other window, where the client area should not be resized, but the overall window size should be increased.
BOOL GetResize() const
Returns whether the message bar's host window will be resized to accommodate the message bar.
static CCtrlMessageBar* GetMessageBarCtrl(CWnd* pCtrl);
Returns a pointer to the CCtrlMessageBar
currently attached to the specified Windows control.
void SetShowMenuCallback(PFNSHOWMENUCALLBACK pfnShowMenuCallback)
Sets a callback function to display a context menu. See the PFNSHOWMENUCALLBACK
type documentation below, for details.
Types
typedef BOOL (*PFNSHOWMENUCALLBACK)(CCtrlMessageBar* pBar, CWnd* pCtrl, UINT nHitTest, CPoint point);
The type of the callback function used to display a context menu. The callback function is called when the user left- or right-clicks on the message bar.
The pBar
parameter is the message bar clicked on. The pCtrl
parameter is the control hosting the message bar. nHitTest
is the hit-test code, which specifies where the user clicked, and will be one of HTMESSAGEBAR
to indicate the body of the bar, or HTMESSAGEBARCLOSE
to indicate the close button. The point
parameter specifies the user's mouse position at the time of click, in window co-ordinates.
The function may show - and act on - a context menu, or may choose to simply allow default handling. Return FALSE
to perform default handling, else TRUE
.
Acknowledgements and References
History
Version 1.0 - 23 Oct 2008
- Added additional information to article text.
Version 1.0 - 08 Oct 2008