Introduction
One of my clients recently showed me a competitor's product that had some very
nice user interface features. One of the features was a groupbox that
displayed a dynamic header and icon - the icon changed to indicate
current status of the information displayed inside the groupbox.
I thought this would be trivial to copy, so naturally I said I could
do it.
Of course I was thinking of the trick I used with displaying a checkbox
in the groupbox header, like I did in my
EnableGroupboxControls
article. By placing a checkbox control over the groupbox header, but
after the groupbox control in tab order, it is possible to make
the checkbox appear to be integrated with the groupbox. My plan was to
do the same thing to display an icon and text over the groupbox header, using
a simple CStatic
-based control.
The result looked very good. I cannot show you how it looked, because I
scrapped this idea in about five seconds. Unlike user-interactive controls
(like buttons), CStatic
controls are assumed not to
change, and this meant that paint messages were not being sent to
CStatic
control in a consistent manner, causing icon
and text to suddenly disappear.
Since this is the way non-interactive controls behaved, my next choice
was to base the new icon control on CButton
. Making the
button ownerdraw and putting all the display code in the
DrawItem()
function, I quickly came up with CXIcon,
which proved to be stable and without the painting problems of earlier
CStatic
control.
Although XIcon is based on CButton
, XIcon
objects do not act like buttons, and they do not display the usual
hot or pressed state.
XIcon Features and Behaviors
The
XIcon demo app shows how
XIcon
control may be used with groupbox:
|
This screenshot shows some of the text effects and icon alignments.
XIcon buttons 1 and 2 use BS_LEFT , and
XIcon buttons 3 and 4 use BS_RIGHT .
The bar that is placed inside each groupbox shows the width of the
XIcon button as it is in the dialog template. In this screenshot,
automatic resizing is turned on, and so the XIcon button resizes
itself to the width of the text and icon.
|
Compare this with the way it looks when automatic resizing is turned off:
|
In this screenshot the XIcon buttons do not resize themselves.
The areas outlined in red show the over-sizing or under-sizing,
which correspond to the size of the bar placed inside the groupbox.
|
With
XIcon I now had a control that I could use with groupbox
or anyplace I needed icon + text.
XIcon API
Function |
Description |
BOOL GetAutoResize() | Retrieves auto resize setting |
BOOL GetBold() | Retrieves bold setting |
CFont* GetFont() | Retrieves pointer to CFont member variable |
BOOL GetFont(LOGFONT *pLF) | Retrieves LOGFONT struct for font |
CString GetFontFaceName() | Retrieves font face name |
int GetFontPointSize() | Retrieves font point size |
int GetIconSpacing() | Retrieves spacing between icon and text |
void GetMargins(int& nXMargin, int& nYMargin) | Retrieves x and y margins |
COLORREF GetTextColor() | Retrieves text color |
void Resize() | Resize control based on current text, font, and icon setting |
CXIcon& SetAutoResize(BOOL bAutoResize, BOOL bRedraw = TRUE) | Sets auto resizing option |
CXIcon& SetBold(BOOL bBold, BOOL bRedraw = TRUE) | Sets bold font |
CXIcon& SetFont(CFont *pFont, BOOL bRedraw = TRUE) | Sets font via CFont object |
CXIcon& SetFont(LOGFONT * pLogFont, BOOL bRedraw = TRUE) | Sets font via LOGFONT struct |
CXIcon& SetFont(LPCTSTR lpszFaceName, int nPointSize, BOOL bRedraw = TRUE) | Sets font face name and point size |
CXIcon& SetIcon(HICON hIcon, UINT nIconSize = 16, BOOL bRedraw = TRUE) | Sets icon via HICON |
CXIcon& SetIcon(UINT nIconId, UINT nIconSize = 16, BOOL bRedraw = TRUE) | Sets icon via resource id |
CXIcon& SetIconSpacing(int nIconSpacing, BOOL bRedraw = TRUE) | Sets spacing between icon and text |
CXIcon& SetMargins(int nXMargin, int nYMargin, BOOL bRedraw = TRUE) | Sets x and y margins |
CXIcon& SetTextColor(COLORREF cr, BOOL bRedraw = TRUE) | Sets text color |
CXIcon& SetWindowText(LPCTSTR lpszText, BOOL bRedraw = TRUE) | Sets text |
Implementation Details
I have already mentioned that
XIcon is based on ownerdraw
CButton
control, and uses
DrawItem()
function to
implement drawing. To reduce flicker to minimum, I also used
DC double-buffering when drawing text and icon.
In demo app, I have used file globe.ico that I extracted from
image library that is included with Visual Studio®. This library
(VS2005ImageLibrary.zip) is located in Common7 directory.
The only other thing you should be aware of is that you can supply icon to
XIcon either by passing HICON
handle, or by passing
resource id of icon. If passing icon, it is up to caller to make
sure icon is destroyed; if passing resource id, XIcon
will call DestroyIcon()
itself.
Note that there is no linkage between
XIcon buttons and groupboxes.
Here is dialog template for demo app, with groupbox/XIcon
pairs highlighted:
Overlaying the groupbox header with XIcon will work only if you
know the trick: the groupbox must precede XIcon in tab order.
Tab order is simply the order in which controls appear in the dialog template.
If the groupbox came after XIcon, it would overlay XIcon,
and the XIcon would not be visible to the user. This works this way
because the order of controls in the dialog template is also the order
in which the controls are created and displayed at run time.
You can set the tab order inside Visual Studio
®, but with overlapping controls
it is not completely straightforward. Here is what the dialog template looks
like with tab order labels made visible by
menu command
Format |
Tab Order:
The groupbox headers that are outlined in red are the ones that have been
overlaid by
XIcon controls. Since the tab order labels for
groupboxes and
XIcon controls
are displayed over each other, it is difficult to set the tab order in
VS IDE. However, as usual, there is a trick: temporarily move the
XIcon control
above or below the groupbox, and then edit the tab order. When you are
finished, move
XIcon control back.
How to use
Step 1 - Add Files
To integrate XIcon into your app, you first need to
add following files to your project:
Step 2 - Add Header File to Your Source Module
In the module where you want to use
XIcon (typically this
will be dialog header file), include header file
XIcon.h .
Step 3 - Add Button Control to Dialog
Using the VS IDE resource editor, add a button control where you want the
XIcon control (make sure to select
BS_OWNERDRAW
style),
and associate button control
with a class variable. Then replace
CButton
with
CXIcon
:
Step 4 - Add Initialization Code
Add initialization code to
OnInitDialog()
function:
m_IconButton1.SetBold(TRUE, FALSE);
m_IconButton2.Resize();
m_IconButton3.SetFont(_T("Comic Sans MS"), 10, FALSE);
m_IconButton4.SetWindowText(MAKEINTRESOURCE(IDS_BUTTON_4), FALSE)
.SetTextColor(RGB(0,0,255), FALSE);
LoadIcons();
LoadIcons()
is function to load selected icon:
void CXIconTestDlg::LoadIcons()
{
UpdateData(TRUE);
if (m_hGlobeIcon)
::DestroyIcon(m_hGlobeIcon);
m_hGlobeIcon = 0;
static UINT nIconId[] = { IDI_T256, IDI_T32BPP };
if (m_nStyle == 0 || m_nStyle == 2)
{
m_hGlobeIcon = (HICON) ::LoadImage(AfxGetInstanceHandle(),
MAKEINTRESOURCE(nIconId[m_nIconType]),
IMAGE_ICON, 16, 16, 0);
ASSERT(m_hGlobeIcon);
}
m_IconButton1.SetIcon(m_hGlobeIcon);
m_IconButton2.SetIcon(m_hGlobeIcon);
m_IconButton3.SetIcon(m_hGlobeIcon);
m_IconButton4.SetIcon(m_nStyle == 1 ? 0 : nIconId[m_nIconType]);
}
Revision History
Version 1.0 - 2008 May 4
Usage
This software is released into the public domain. You are free to use it
in any way you like, except that you may not sell this source code. If you
modify it or extend it, please to consider posting new code here for everyone
to share. This software is provided "as is" with no expressed or implied
warranty. I accept no liability for any damage or loss of business that
this software may cause.