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 ucancode.neter 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.
- Will be easier to
change options dynamically, as shown in demo
application.
- Will be easier to
manage selection events, also shown in 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, "ensure that
'owner draw fixed' property is active".
- Create or modify an
OnInitDialog
event, and subclass 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 make WS_BORDER
style "off".
CRadioListBox
internals
CRadioListBox
class is derived from 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 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:
Collapse
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 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;
}