Introduction
Recently I had discussed in a Visual C++ forum about a member's request to implement a custom ListBox
control similar to CCheckListBox
, but with radio buttons. Initially it appeared to be trivial, since the ListBox
control's unique selection version complies with asker requirements, but I have concluded that this control has some advantages:
- It is clearer, with radio buttons, that options are mutually exclusive.
- Is a good alternative to a group of radio buttons, because you have to maintain just one control.
- It inherits some useful features like scrolling, sort and multi-column.
- It will be easier to change options dynamically, as shown in the demo application.
- It will be easier to manage selection events, also shown in the demo application.
Using the Code
To implement CRadioListBox
into your project, you just need to do a few steps:
- Include RadioListBox.cpp and RadioListBox.h in your project.
- Insert a
CRadioListBox
object into your dialog class declaration (*.h file).
- Put a standard
ListBox
control into your dialog's template layout, ensuring that the "owner draw fixed" property is active.
- Create or modify an
OnInitDialog
event and subclass a corresponding ListBox
.
For example, if your dialog is named CMyDialog
, the ListBox
member is m_RadioListBox
and the control ID is IDC_RADIOLISTBOX
, then you can subclass the control in the following way:
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
m_RadioListBox.SubclassDlgItem(IDC_RADIOLISTBOX, this);
return TRUE;
}
There are other ways to subclass a control in a MFC application, as explained by Eric Sanchez in his article "Control Subclassing," but I think the above version is the shortest one.
Transparency
As you can see in the above pictures, there is a transparency feature, so the ListBox
can imitate radio button's aspect. This can be easily done by setting the WS_EX_TRANSPARENCY
attribute of the control in the Visual C++ Dialog Editor. Also, it will be necessary to turn WS_BORDER
's style "off."
CRadioListBox Internals
The CRadioListBox
class is derived from the CListBox
class with just one derived method: DrawItem
. The method does not highlight the selected item as in a standard ListBox
control, but draws a radio button instead. It also manages the focus state to draw the focus rectangle properly and the background color according to the transparency attribute. I know it could have been better, but this first version runs OK under different screen conditions. Here is the source code:
void CRadioListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
if (lpDrawItemStruct->itemID == (UINT)-1)
{
if (lpDrawItemStruct->itemAction & ODA_FOCUS)
pDC->DrawFocusRect(&lpDrawItemStruct->rcItem);
return;
}
else
{
int selChange = lpDrawItemStruct->itemAction & ODA_SELECT;
int focusChange = lpDrawItemStruct->itemAction & ODA_FOCUS;
int drawEntire = lpDrawItemStruct->itemAction & ODA_DRAWENTIRE;
if (selChange || drawEntire)
{
BOOL sel = lpDrawItemStruct->itemState & ODS_SELECTED;
pDC->FillSolidRect(&lpDrawItemStruct->rcItem,
::GetSysColor((GetExStyle()&WS_EX_TRANSPARENT)?
COLOR_BTNFACE:COLOR_WINDOW));
int h =
lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top;
CRect rect(lpDrawItemStruct->rcItem.left+2,
lpDrawItemStruct->rcItem.top+2,
lpDrawItemStruct->rcItem.left+h-3,
lpDrawItemStruct->rcItem.top+h-3);
pDC->DrawFrameControl(&rect, DFC_BUTTON,
DFCS_BUTTONRADIO | (sel?DFCS_CHECKED:0));
pDC->SetTextColor(COLOR_WINDOWTEXT);
pDC->SetBkMode(TRANSPARENT);
lpDrawItemStruct->rcItem.left += h;
pDC->DrawText((LPCTSTR)lpDrawItemStruct->itemData,
&lpDrawItemStruct->rcItem, DT_LEFT);
}
if (focusChange || (drawEntire &&
(lpDrawItemStruct->itemState & ODS_FOCUS)))
pDC->DrawFocusRect(&lpDrawItemStruct->rcItem);
}
}
To achieve the transparency feature, it will be necessary to control the background painting too, by handling the WM_CTLCOLOR
message:
HBRUSH CRadioListBox::CtlColor(CDC* pDC, UINT nCtlColor)
{
if ( (GetExStyle()&WS_EX_TRANSPARENT) && nCtlColor==CTLCOLOR_LISTBOX)
return (HBRUSH)::GetSysColorBrush(COLOR_BTNFACE);
return NULL;
}
History
- March 29th, 2005. First version.
- April 6th, 2005. Added transparency feature.