65.9K
CodeProject is changing. Read more.
Home

CRadioListBox: A ListBox with Radio Buttons (MFC version)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (14 votes)

Mar 29, 2005

CPOL

2 min read

viewsIcon

134358

downloadIcon

4300

How to implement an owner-drawn ListBox with radio buttons instead of standard selection highlight

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

    // 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 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);
    // 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 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 transparent style selected...
    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.