Click here to Skip to main content
Click here to Skip to main content

Controls-in-controls: An edit box with an icon

, 16 Jun 2004 Public Domain
Rate this:
Please Sign up or sign in to vote.
Adding controls to a CEdit, an edit box with an icon

Sample Image - editwithicon.gif

Introduction

While adding controls to other controls might be a commonly desired task, and edit box with an icon is perhaps not the most exciting or useful class. It presents a few enjoyable challenges, however, and usefulness is certainly not the only criteria for diving into control subclassing in MFC.

CIconEdit is a small CEdit-derived class with an attached small icon, where the multi line edit box edit rectangle is exploited, instead of the normal handling of non-client areas.

Using the code

Include the cpp- and h-file to the project. CIconEdit can be instantiated either dynamically, by a call to Create, or from a dialog template. In the latter case, a control variable can be created and bound to the control with the class wizard, or SubclassDlgItem can be used. Call SetIcon with either a HICON or a resource id to set the icon to display.

CIconEdit uses the edit rectangle of multi line edit boxes. Basically, it modifies the edit rectangle to add a left margin wide enough to accommodate a small icon. An instance of CIconWnd - a CStatic-derived class - is used to paint the icon. Icons can be set by calling CIconEdit::SetIcon with either a HICON handle to the desired icon, or a UINT resource id. If a resource id is used, the icon is destroyed by CIconEdit, otherwise the caller will have to destroy it.

The class overrides PreSubclassWindow. This call checks the edit box style. If ES_MULTILINE is not set, SetRect will not work, so the function ASSERTs. Sadly, it is not possible to add this style after the control is created - and it is too early to destroy and recreate the it. Then the edit rectangle is established. The width of a small icon is retrieved by a call to GetSystemMetrics, and the current edit rectangle is fetched, updated and set back to the control.

As soon as an icon is set by a call to either of the two SetIcon:s, the icon window is created.

CIconWnd is a very simple CStatic-derived class, handling WM_PAINT and there drawing the icon with a call to ::DrawIconEx. The icon will be scaled to the system small icon size if necessary.

Points of interest

Instantiating this control, doing a few common operations on CEdits, displayed an understandable but annoying quirk - the edit rectangle is reset. As the rectangle is absolute for a CEdit (RTF-controls can use an offset), WM_SIZE will have to be handled anyway. More surprising was that WM_SETFONT also killed the edit rectangle. For this reason, CIconEdit handles WM_SIZE and WM_SETFONT. As SetFont is not virtual, the message itself will have to be handled.

In this case, using a separate control for the icon might seem a bit byzantine, why not just draw it in the non-client area paint handling? Well, now I got myself a small-icon control if I should ever need one (which I doubt), and the edit control and icon is thoroughly separated codewise.

But the more exciting implication is the possibility to use the edit rectangle to put other controls and/or visuals in the edit box, buddy-buttons, line-numbers etc. And as the editing rectangle is not only used by the plain vanilla edit box - the RTF-control also has one - there is the possibility to add rulers etc. this way.

History

15/6 2004

In an explosion of activity, this article is finally updated thanks to the feedback from David Pritchard . I'm getting the system background color using GetSysColor(COLOR_WINDOW) when clearing the background behind the icon, and SetIcon with the HICON parameter will now also create the icon control... *blushing with embarrassment*

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

Share

About the Author

Johan Rosengren
Software Developer (Senior) Abstrakt Mekanik AB
Sweden Sweden
45 years old, married, three kids.
 
Started with computers more than 20 years ago on a CBM-64.
 
Read Theoretical Philosophy at the University of Lund.
 
Working as a C++ consultant developer.
 
Science-fiction freak. Enjoy vintage punkrock.

Comments and Discussions

 
Questionsource code for Buddy Button "404 - File or directory not found" in http://www.gipsysoft.com/articles/BuddyButton PinmemberAPache_hema_317-Mar-14 13:05 
QuestionScrolling ? PinmemberMattthe cooldude9-Mar-05 13:58 
AnswerRe: Scrolling ? PinmemberJohan Rosengren10-Mar-05 5:31 
GeneralProblem with mouse clicks PinmemberDavid Pritchard19-Oct-04 23:01 
GeneralFalse alarm? Plus non-client implementation of IconEdit PinmemberDavid Pritchard20-Oct-04 3:47 
OK, folks, first I need to apologise because I think the bug I saw was caused by something else. What happened was that repainting in my edit control stopped working properly. All my fix did was stop the insertion point from changing when the icon is clicked, which you may (or may not) feel is a worthwhile modification. The bug itself remains unexplained. It's happened to other users of my app before, but in my case it vanished after I restarted Windows (but not the application). I don't have any good theories and I haven't found any references to the problem, so for now I'm going to ignore it. Incidentally, the problem still occurs if I change the CIconEdits to normal CEdits, so CIconEdit is innocent in this respect.
 
Nevertheless, all this spurred me to try another approach to the icon-in-edit problem, using the buddy button code from GipsySoft. At least this has the merit of being usable in password edit controls. Here's the code. It's Johan's code, but with a few modifications and bits stripped out. The end result is quite a bit simpler than the original CIconEdit. It uses the GipsySoft EnableBuddyButton function from http://www.gipsysoft.com/articles/BuddyButton/[^], which I haven't included. It's just a question of dropping in the header file.
 
There's not much to comment on. First, I found that, in the same function, I needed to do a quick calculation to centre the icon vertically. Second, the icon must be created as a child of the edit's parent in order for the buddy button code to work in unaltered form. Otherwise you need to fiddle with the latter, which I didn't want to do.
 
My modifications are marked with "DP" throughout.
 
I hope somebody finds this useful!
 
Header:

class CIconWnd : public CStatic
{
// Construction/destruction
public:
CIconWnd();
virtual ~CIconWnd();
 
// Operations
public:
void SetIcon( HICON icon );
 
protected:
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
 
private:
// Attributes
HICON m_icon;
};
 
/////////////////////////////////////////////////////////////////////////////
// CIconEdit window
 
class CIconEdit : public CEdit
{
// Construction/destruction
public:
CIconEdit();
virtual ~CIconEdit();
 
// Operations
public:
void SetIcon(HICON icon);
void SetIcon(UINT iconres);
 
protected:
 
DECLARE_MESSAGE_MAP()
 
private:
// DP: 27/05/2004 Placed create icon window code in separate function, called from both SetIcon functions
void CreateIconWindow();
 
// Attributes
CIconWnd m_icon;
HICON m_internalIcon;
};

 
Body:

CIconEdit::CIconEdit()
/*============================================================
Function : CIconEdit::CIconEdit
 
Description : constructor
 
Return : void
Parameters : none
 
Usage :
============================================================*/
{
 
m_internalIcon = NULL;
m_icon.m_hWnd = NULL;
 
}
 
CIconEdit::~CIconEdit()
/*============================================================
Function : CIconEdit::~CIconEdit
 
Description :
Return : destructor -
Parameters :
Usage :
============================================================*/
{
 
// Clean up
if( m_internalIcon )
::DestroyIcon( m_internalIcon );
 
}
 
BEGIN_MESSAGE_MAP(CIconEdit, CEdit)
END_MESSAGE_MAP()
 

/////////////////////////////////////////////////////////////////////////////
// CIconEdit implementation
 
void CIconEdit::SetIcon( HICON icon )
/*============================================================
Function : CIconEdit::SetIcon
 
Description : Sets a new icon and updates the control

Return : void
Parameters : HICON icon - Handle to the icon
 
Usage : The caller must destroy the icon
 
============================================================*/
{
// DP: 27/05/2004 Placed create icon window code in separate function, called from both SetIcon functions
CreateIconWindow();
 
// Update the icon control
m_icon.SetIcon( icon );
 
}
 
void CIconEdit::SetIcon( UINT iconres )
/*============================================================
Function : CIconEdit::SetIcon
 
Description : Sets a new icon and updates the control

Return : void
Parameters : UINT iconres - Resource id of the icon
 
Usage : The class will load and destroy the icon.
 
============================================================*/
{
// DP: 27/05/2004 Placed create icon window code in separate function, called from both SetIcon functions
CreateIconWindow();
 
// If we already have an icon, we destroy it
if( m_internalIcon )
::DestroyIcon( m_internalIcon );
 
// Loading the new icon
m_internalIcon = ( HICON ) ::LoadImage( AfxGetResourceHandle(),
MAKEINTRESOURCE( iconres ),
IMAGE_ICON,
16,
16,
LR_DEFAULTCOLOR );
 
ASSERT( m_internalIcon != NULL );
 
// Update the icon control
m_icon.SetIcon( m_internalIcon );
 
}
 
// DP: 27/05/2004 Placed create icon window code in separate function, called from both SetIcon functions
void CIconEdit::CreateIconWindow()
{
if( !m_icon.m_hWnd )
{
CRect rectWindow;
GetWindowRect(&rectWindow);

// If the icon window doesn't exist,
// we create it
CRect iconRect( 0, 0, GetSystemMetrics( SM_CXSMICON ), rectWindow.Height() );
if(m_icon.m_hWnd==NULL)
// DP: 03/06/2004 Make string Unicode-compatible
// DP: 20/10/2004 Must create as child of parent, otherwise buddy code fails
m_icon.Create(_T(""), WS_CHILD | WS_VISIBLE, iconRect, GetParent(), 1 );
 
// Make buddy window
if (m_icon.m_hWnd)
{
EnableBuddyButton(GetSafeHwnd(), m_icon.m_hWnd, BBS_LEFT);
}
}
}
 

/////////////////////////////////////////////////////////////////////////////
// CIconEdit implementation
 
/////////////////////////////////////////////////////////////////////////////
// CIconWnd
 

CIconWnd::CIconWnd()
/*============================================================
Function : CIconWnd::CIconWnd
 
Description : constructor

Return : void
Parameters :
 
Usage :
 
============================================================*/
{
 
m_icon = NULL;
 
}
 
CIconWnd::~CIconWnd()
/*============================================================
Function : CIconWnd::~CIconWnd
 
Description : destructor

Return : void
Parameters : none
 
Usage :
 
============================================================*/
{
}
 
BEGIN_MESSAGE_MAP(CIconWnd, CStatic)
ON_WM_PAINT()
ON_WM_ERASEBKGND()
 
// // DP: 10/06/2004 Added to set background colour of icon properly
// ON_WM_CTLCOLOR()
END_MESSAGE_MAP()
 
/////////////////////////////////////////////////////////////////////////////
// CIconWnd message handlers
 
void CIconWnd::OnPaint()
/*============================================================
Function : CIconWnd::OnPaint
 
Description : Mapped to WM_PAINT. Draws the icon with the
small icon size (regardless of original)

Return : void
Parameters : none
 
Usage : Called from Windows
 
============================================================*/
{
 
CPaintDC dc( this );
if( m_icon )
{
CRect rect;
GetClientRect( &rect );
 
// Clearing the background
// dc.FillSolidRect( rect, dc.GetBkColor() );
 
// DP: 10/06/2004 Fixed background colour of icon
dc.FillSolidRect( rect, GetSysColor(COLOR_WINDOW) );
 
// Drawing the icon
int width = GetSystemMetrics( SM_CXSMICON );
int height = GetSystemMetrics( SM_CYSMICON );
 
// DP: 20/10/2004 Calculate Y-Pos
int nYPos = (rect.Height() - height) / 2;
 
// ::DrawIconEx( dc.m_hDC, 1, 1, m_icon, width, height, 0, NULL, DI_NORMAL );
::DrawIconEx( dc.m_hDC, 1, nYPos, m_icon, width, height, 0, NULL, DI_NORMAL );
}
}
 
BOOL CIconWnd::OnEraseBkgnd( CDC* pDC)
/*============================================================
Function : CIconWnd::OnEraseBkgnd
 
Description : Mapped to the WM_ERASEBKGND

Return : BOOL - Always TRUE
Parameters : CDC* - From Windows

Usage : Called from Windows
 
============================================================*/
{
return TRUE;
}
 
/////////////////////////////////////////////////////////////////////////////
// CIconWnd implementation
 
void CIconWnd::SetIcon( HICON icon )
/*============================================================
Function : CIconWnd::SetIcon
 
Description : Called to set/change the icon

Return : void
Parameters : HICON icon - icon to use

Usage : The function is called from the CEditIcon
class
 
============================================================*/
{
m_icon = icon;
if( ::IsWindow( m_hWnd ) )
RedrawWindow();
}
 

 
(Incidentally, I'm sure this has been asked a million times, but is there no way to post properly formatted code, i.e. with tabs/spaces preserved, in these messages? I spent ten or fifteen minutes looking for the answer but couldn't find it.)
GeneralRe: False alarm? Plus non-client implementation of IconEdit PinmemberJohan Rosengren20-Oct-04 6:17 
GeneralRe: False alarm? Plus non-client implementation of IconEdit PinmemberDavid Pritchard20-Oct-04 9:38 
GeneralRe: False alarm? Plus non-client implementation of IconEdit PinmemberJohan Rosengren21-Oct-04 8:17 
GeneralControls-in-controls: An edit box with an icon PinmemberLarsson23-Sep-04 22:49 
GeneralRe: Controls-in-controls: An edit box with an icon PinmemberJohan Rosengren23-Sep-04 23:20 
Generalfor an in depth discussion see also... PinmemberAmun-Ra19-Jun-04 3:13 
GeneralRe: for an in depth discussion see also... PinmemberJohan Rosengren19-Jun-04 3:19 
GeneralA tiny, tiny painting correction PinmemberDavid Pritchard10-Jun-04 10:12 
GeneralRe: A tiny, tiny painting correction PinmemberJohan Rosengren10-Jun-04 10:16 
GeneralBug! SetIcon(HICON) PinmemberDavid Pritchard27-May-04 3:15 
GeneralRe: Bug! SetIcon(HICON) PinmemberJohan Rosengren27-May-04 3:55 
GeneralRe: Bug! SetIcon(HICON) PinmemberDavid Pritchard27-May-04 10:44 
GeneralRe: Bug! SetIcon(HICON) PinmemberJohan Rosengren27-May-04 19:09 
GeneralRe: Bug! SetIcon(HICON) PinmemberDavid Pritchard28-May-04 12:02 
GeneralRe: Bug! SetIcon(HICON) PinmemberJohan Rosengren28-May-04 21:38 
GeneralRe: Bug! SetIcon(HICON) PinmemberDavid Pritchard29-May-04 4:01 
GeneralRe: Bug! SetIcon(HICON) PinmemberJohan Rosengren29-May-04 4:27 
GeneralRe: Some comments PinmemberJohan Rosengren10-Mar-04 8:23 
Generalthe ES_MULTILINE issue Pinmember.dan.g.9-Mar-04 23:47 
GeneralRe: the ES_MULTILINE issue PinmemberJohan Rosengren10-Mar-04 0:02 
GeneralRe: the ES_MULTILINE issue Pinmember.dan.g.10-Mar-04 0:35 
GeneralRe: the ES_MULTILINE issue PinmemberJohan Rosengren10-Mar-04 8:28 
Generalinteresting Pinmember.dan.g.9-Mar-04 23:43 
GeneralRe: interesting Pinmembergrigri18-Mar-04 11:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 17 Jun 2004
Article Copyright 2004 by Johan Rosengren
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid