Free since 2005 · No login required
AT

Academic Tutorials

Learn at your own pace

site-mobile-top-banner · 320x50

Custom CListBox - A ListBox with Radio Box, DrawFrameControl, DrawFocusRect

Added 29 Jul 2008

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.
  • 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();

// Some other custom source code here

m_RadioListBox.SubclassDlgItem(IDC_RADIOLISTBOX, this);

return TRUE; // return TRUE unless you set the focus to a control
}

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);
// just draws focus rectangle when listbox is empty
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;
// Draws background rectangle, color depends on transparency
pDC->FillSolidRect(&lpDrawItemStruct->rcItem,
::GetSysColor((GetExStyle()&WS_EX_TRANSPARENT)?
COLOR_BTNFACE:COLOR_WINDOW));
// Draw radio button
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));
// Draws item text
pDC->SetTextColor(COLOR_WINDOWTEXT);
pDC->SetBkMode(TRANSPARENT);
lpDrawItemStruct->rcItem.left += h;
pDC->DrawText((LPCTSTR)lpDrawItemStruct->itemData,
&lpDrawItemStruct->rcItem, DT_LEFT);
}
// draws focus rectangle
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 transparent style selected...
if ( (GetExStyle()&WS_EX_TRANSPARENT) && nCtlColor==CTLCOLOR_LISTBOX)
return (HBRUSH)::GetSysColorBrush(COLOR_BTNFACE);

return NULL;
}