Introduction
The Combo box became a standard toolbar feature. Having a combo box is convenient to display a frequently accessed set of choices, or to keep a history of user input (for example, most recent search patterns).
Embedding a standard combo box in a toolbar is fairly straightforward. However, the result will probably not look satisfactory, unless you perform a few more steps. In this article we will show these steps, so you will be able to achieve the following:
- Add one or more combo boxes to a toolbar;
- Adjust the combo box font;
- Make the combo box respond to user commands;
- Create a history combo box that contains the most recent user entries.
Embedding combo box into a toolbar
To embed a combo box into a toolbar you�ll need to add code to the CMainFrame::OnCreate
message handler. To avoid adding low-level toolbar details to the OnCreate
method, you can encapsulate them in a dedicated method CreateComboBox
that is added to your custom CToolBar
-based class:
class CToolBarWithCombo : public CToolBar
{
public:
CComboBox m_comboBox;
BOOL CreateComboBox(class CComboBox& comboBox,
UINT nIndex,
UINT nID,
int nWidth,
int nDropHeight);
protected:
DECLARE_MESSAGE_MAP()
};
BOOL CToolBarWithCombo::CreateComboBox(CComboBox& comboBox,
UINT nIndex,
UINT nID,
int nWidth,
int nDropHeight)
{
SetButtonInfo(nIndex, nID, TBBS_SEPARATOR, nWidth);
CRect rect;
GetItemRect(nIndex, &rect);
rect.top = 1;
rect.bottom = rect.top + nDropHeight;
if (!comboBox.Create(CBS_DROPDOWN|WS_VISIBLE|WS_TABSTOP|WS_VSCROLL,
rect, this, nID))
{
TRACE("Failed to create combo-box\n");
return FALSE;
}
return TRUE;
}
Then you can call the CreateComboBox
method right after your custom toolbar is created (inside CMainFrame::OnCreate
):
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1;
}
if (!m_wndToolBar.CreateComboBox(m_wndToolBar.m_comboBox, 3, ID_COMBO, 150, 100))
{
TRACE0("Failed to create toolbar's combo box\n");
return -1;
}
The alternative is to override CToolBarWithCombo::OnCreate
and place the CreateComboBox
call there.
Font adjustment
By default, the combo box is assigned the system font which does not look very good in toolbars. So the next logical step is to make the combo box look better. You can add font selection code directly to CreateComboBox
method, but since there are more features we plan to add to our combo boxes, we�ll derive a new class from CComboBox
:
class CSmartComboBox : public CComboBox
{
protected:
CFont m_font;
public:
CSmartComboBox();
LONG m_lfHeight;
LONG m_lfWeight;
CString m_strFaceName;
BOOL CreateFont(LONG lfHeight, LONG lfWeight, LPCTSTR lpszFaceName);
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
CSmartComboBox::CSmartComboBox()
{
m_lfHeight = -10;
m_lfWeight = FW_NORMAL;
m_strFaceName = _T("MS Sans Serif");
m_nMaxStrings = 10;
}
BEGIN_MESSAGE_MAP(CSmartComboBox, CComboBox)
ON_WM_CREATE()
END_MESSAGE_MAP()
int CSmartComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CComboBox::OnCreate(lpCreateStruct) == -1)
return -1;
if( !CreateFont(m_lfHeight, m_lfWeight, m_strFaceName) )
{
TRACE0("Failed to create font for combo box\n");
return -1;
}
return 0;
}
BOOL CSmartComboBox::CreateFont(LONG lfHeight, LONG lfWeight, LPCTSTR lpszFaceName)
{
LOGFONT logFont;
memset(&logFont, 0, sizeof(logFont));
if (!::GetSystemMetrics(SM_DBCSENABLED))
{
logFont.lfHeight = lfHeight;
logFont.lfWeight = lfWeight;
CString strDefaultFont = lpszFaceName;
lstrcpy(logFont.lfFaceName, strDefaultFont);
if (!m_font.CreateFontIndirect(&logFont))
{
TRACE("Could Not create font for combo\n");
return FALSE;
}
SetFont(&m_font);
}
else
{
m_font.Attach(::GetStockObject(SYSTEM_FONT));
SetFont(&m_font);
}
return TRUE;
}
Now the combo box can easily be adjusted to reflect the style of your application.
Adding response to user commands
Once combo box is embedded into a toolbar, its properties can be accessed in a traditional manner. But in order to provide prompt response to user commands, embedded combo boxes need to cooperate with their container, i.e. the toolbar. Then, the user will be able to type some text into a combo box, press "Enter", and the corresponding command is sent to an application.
We will demonstrate this step with an application that has two combo boxes embedded in a toolbar. This is important to verify that the command is fetched by the right combo box.
class CToolBarWithCombo : public CToolBar
{
public:
CSmartComboBox m_comboBox1;
CSmartComboBox m_comboBox2;
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
}
BOOL CToolBarWithCombo::OnCommand(WPARAM wParam, LPARAM lParam)
{
if( wParam == IDOK && lParam == 0 )
{
CString strText;
CString strCommand;
CComboBox* rgComboBox[] = {&m_comboBox1, &m_comboBox2};
for( int index = 0;
index < sizeof(rgComboBox) / sizeof(rgComboBox[0]);
++index )
{
if( rgComboBox[index]->GetEditSel() != 0 )
{
rgComboBox[index]->GetWindowText(strText);
strCommand.Format(_T("Command from ComboBox[%d]: %s"),
index+1,
(LPCTSTR)strText);
AfxMessageBox(strCommand);
rgComboBox[index]->AddString(strText);
rgComboBox[index]->SetWindowText(_T(""));
}
}
}
return CToolBar::OnCommand(wParam, lParam);
}
As you can see, the trick to find which combo box has focus is to call the GetEditSel
function. Unless the cursor is placed in a combo box, the function will return 0.
Managing combo box entries
The last of our improvements is to create a combo box that will manage recent user entries. Such combo boxes are typically populated with the history of most recent search patterns, dialed phone numbers etc. They have the following common features:
- Maximum number of items is limited, once this number is reached, new entries replace oldest entries;
- Entries are added to the top of the list;
- List does not contain duplicates.
Here is the code you�ll need to add to CSmartComboBox
:
class CSmartComboBox : public CComboBox
{
public:
int m_nMaxStrings;
int AddString(LPCTSTR str);
int InsertString(int index, LPCTSTR str);
};
int CSmartComboBox::AddString(LPCTSTR str)
{
if( _tcslen(str) == 0 )
return -1;
int oldIndex = FindStringExact(-1, str);
if( oldIndex >= 0 )
DeleteString(oldIndex);
if( GetCount() == m_nMaxStrings )
DeleteString(m_nMaxStrings-1);
return CComboBox::InsertString(0, str);
}
int CSmartComboBox::InsertString(int index, LPCTSTR str)
{
if( _tcslen(str) == 0 )
return -1;
int oldIndex = FindStringExact(-1, str);
if( oldIndex >= 0 )
{
DeleteString(oldIndex);
if( index >= oldIndex )
--index;
}
if( GetCount() == m_nMaxStrings )
DeleteString(m_nMaxStrings-1);
return CComboBox::InsertString(index, str);
}
The sample project that follows this article contains source code and a test application that demonstrates embedded combo boxes.