Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / MFC

A Separator Combo-box

Rate me:
Please Sign up or sign in to vote.
4.51/5 (16 votes)
13 Jun 2004CPOL5 min read 141.3K   1.8K   38   29
A CComboBox derived combo-box class

Introduction

Recently, I had a project requirement based on the current code to enhance a combobox with some separators, setting its items apart from different meanings, such as an example shown as below:

zsepcmb/sepcmb1.jpg

By searching websites, I saw many guys also looking for such an adapted combo box control, while I was not able to find an ideal match to my use exactly. In Code Project, the article A Custom Group Combo Box created by Brett R. Mitchell looks good, as it groups its items with distinguishing lines and headers. To fit my use, I might make a header empty and leave a full width line as a separator.

However, this custom combo box called CDropButton uses a CButton class as the main class, and contains a CWnd and a CListbox for data display, which is not a class derived from the standard CCombobox. Thus there is no way to combine it into our current code base as a genuine combo box, since we use CCombobox methods and events frequently. Also as no edit box in that custom control, it loses the editable feature.

Therefore, I decided to write a CCombobox derived class called CSeparatorComboBox to fit my requirement. By using it, I only need to change the class type in our current code base to maintain its functionality without touch others, and then add two separators with a new method like this:

C++
m_ctrlCombo.SetSeparator(0);
m_ctrlCombo.SetSeparator(-1);

Where m_ctrlCombo is an object of CSeparatorComboBox and SetSeparator(0) adds a separator after the first item indexed by zero as after “All Fruits” in the figure. SetSeparator(-1) is for the second separator positioned one item before last. Its alternative can be SetSeparator(5).

Now any CCombobox event is available. When selecting the item “Apple”, I can receive ON_CBN_SELCHANGE and show the result here in my test program:

zsepcmb/sepcmb2.jpg

Editing it to “Apple-2” is responded by ON_CBN_EDITCHANGE with the result below:

zsepcmb/sepcmb3.jpg

Main Implementations

The main point for the separator combo box design is that the separator should not be selected either from UI or in the program logic. Generally, we may have two choices: First, let a separator occupy some space similar to an item but no actual selection to it. This is usually implemented in the owner draw style to manually draw both text and separator, just as what CDropButton did.

The second way is simply drawing a line between items in the dropdown list, with the same height for each item and no extra space for the separator. The owner draw in this case is not necessary. For my problem, this seems easier and enough.

To draw a line between items without the owner draw style, I have to decide which message handler is appropriate, and how to get a handle to the dropdown list and its device-context as well. Recommended by Microsoft KB article Q174667, I decide to intercept the WM_CTLCOLOR message to subclass CListBox inside CComboBox in my CSeparatorComboBox and do the drawing as follows:

C++
HBRUSH CSeparatorComboBox::OnCtlColor(CDC* pDC, 
      CWnd* pWnd, UINT nCtlColor)
{
   if (nCtlColor == CTLCOLOR_LISTBOX)
   {
      if (m_listbox.GetSafeHwnd() ==NULL)
      {
         m_listbox.SubclassWindow(pWnd->GetSafeHwnd());
      }
      
      CRect r;
      int   nIndex, n = m_listbox.GetCount();
      CPen pen(m_nPenStyle, m_nSepWidth, m_crColor), *pOldPen;
      pOldPen = pDC->SelectObject(&pen);
      
      for (int i=0; i< m_arySeparators.GetSize(); i++)
      {
         nIndex = m_arySeparators[i];
         if (nIndex<0) nIndex += n-1;
         
         if (nIndex < n-1)
         {
            m_listbox.GetItemRect(nIndex, &r);
            pDC->MoveTo(r.left+m_nHorizontalMargin, 
              r.bottom-m_nBottomMargin);
            pDC->LineTo(r.right-m_nHorizontalMargin, 
              r.bottom-m_nBottomMargin);
         }
      }
   
      pDC->SelectObject(pOldPen);
   }
   
   return CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);
} 

Note that for subclassing to occur, the dialog box must be painted at least once. At the first time, by calling SubclassWindow(), I construct the m_listbox object alive and then call its GetItemRect() to retrieve an item coordinate specified by m_arySeparators, which is an integer array containing all the separator positions filled by the SetSeparator() function as mentioned earlier. Using pDC to draw a line is so simple. And in cleanup, don’t forget to call UnsubclassWindow() like this:

C++
void CSeparatorComboBox::OnDestroy()
{
   if (m_listbox.GetSafeHwnd() !=NULL)
      m_listbox.UnsubclassWindow();
    
   CComboBox::OnDestroy();
}

Class Interface

The following is the CSeparatorComboBox class.

C++
class CSeparatorComboBox : public CComboBox
{
   DECLARE_DYNAMIC(CSeparatorComboBox)

   CListBox    m_listbox;
   CArray<int> m_arySeparators;

   int         m_nHorizontalMargin;
   int         m_nBottomMargin;
   int         m_nSepWidth;
   int         m_nPenStyle;
   COLORREF    m_crColor;
 
public:
   CSeparatorComboBox();
   virtual ~CSeparatorComboBox();

   void SetSeparator(int iSep);
   void AdjustItemHeight(int nInc=3);
 
   void SetSepLineStyle(int iSep) { m_nPenStyle = iSep; }
   void SetSepLineColor(COLORREF crColor) { m_crColor = crColor; }
   void SetSepLineWidth(int iWidth) { m_nSepWidth = iWidth; }
   void SetBottomMargin(int iMargin) { m_nBottomMargin = iMargin; }
   void SetHorizontalMargin(int iMargin) { m_nHorizontalMargin = iMargin; }

protected:
   DECLARE_MESSAGE_MAP()

public:
   afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
   afx_msg void OnDestroy();
}; 

To consider visual effect on a separator between items, I provide a function to adjust the item height as follows. It increases an item height based on the current item’s relative height, the default parameter nInc is three pixels.

C++
void CSeparatorComboBox::AdjustItemHeight(int nInc/*=3*/)
{
  SetItemHeight(0, nInc+ GetItemHeight(0));
}

When the first separator is set, SetSeparator() automatically adjusts the default item height for you. But you can call AdjustItemHeight() to overwrite the default. If no separator set, there is no change to the item height.

C++
void CSeparatorComboBox::SetSeparator(int iSep)
{
   if (!m_arySeparators.GetSize())
      AdjustItemHeight();

   m_arySeparators.Add(iSep);
}

The rest of the five functions are used to set the separator attributes:

  • SetSepLineStyle() sets a separator’s line style (PS_SOLID=0, PS_DASH=1, PS_DOT=2, etc.), default to dot line.
  • SetSepLineColor() sets a separator’s color, default to dark gray.
  • SetSepLineWidth() sets a separator’s line width, default to 1 pixel.
  • SetBottomMargin() sets a separator’s bottom margin, default to 2 pixels.
  • SetHorizontalMargin() sets a separator’s horizontal margin, default to 2 pixels.

Using CSeparatorComboBox

To use CSeparatorComboBox, just copy two source files, SepComboBox.h and SepComboBox.cpp to your project and include SepComboBox.h wherever you want. Define a variable like:

C++
CSeparatorComboBox m_ctrlCombo;

In an initialization procedure, add text strings using CComboBox methods:

C++
m_ctrlCombo.AddString("All Fruits");
m_ctrlCombo.AddString("Banana");
m_ctrlCombo.AddString("Orange");
m_ctrlCombo.AddString("Apple");
m_ctrlCombo.AddString("Pear");
m_ctrlCombo.AddString("Watermelon");
m_ctrlCombo.AddString("*Add/Edit Fruit");

Next, set separator positions like this:

C++
m_ctrlCombo.SetSeparator(0);
m_ctrlCombo.SetSeparator(-1);

Optionally, set any attributes you want, such as:

C++
m_ctrlCombo.SetSepLineStyle(PS_SOLID);
m_ctrlCombo.SetSepLineColor(0);
m_ctrlCombo.SetHorizontalMargin(1);

And set a current selection with SetCurSel() and use any CComboBox methods and event handlers. That’s all - what you see at the beginning of this article. For more details, please see CSepComboTestDlg in my demo package. Although the demo is programmed in VC7, it should work the same in the previous Visual C++, as long as MFC is available.

Points of Interest

Recall another way to implement a separator combo box I mentioned with the owner draw style, right? If you want a better UI visually, you may try that idea – no change the current text item height but make room for a separator similar to an item. Override the virtual function or MeasureItem() to manually set positions, draw texts and separators. This might need a bit more work, while a better separator combo box is for sure worthwhile..

History

  • 14th June, 2004: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionLacks implementation Pin
Yunaless8-Jul-16 2:07
Yunaless8-Jul-16 2:07 
AnswerRe: Lacks implementation Pin
Zuoliu Ding8-Jul-16 7:03
Zuoliu Ding8-Jul-16 7:03 
QuestionWindows 7: Seperator isn't drawn properly Pin
Naryoril30-Mar-10 4:48
Naryoril30-Mar-10 4:48 
Hi
First of all thanks for the control.
We recently moved to Windows 7 and there the control has a little issue. When you open the dropdown list the seperators aren't drawn. As soon as you start selecting something with your mouse the separators pop up.
I'v tried to solve it, but haven't found a solution yet.
GeneralRe: Windows 7: Seperator isn't drawn properly Pin
Andrew Truckle16-Apr-20 21:36
professionalAndrew Truckle16-Apr-20 21:36 
AnswerRe: Windows 7: Seperator isn't drawn properly Pin
Andrew Truckle16-Apr-20 21:44
professionalAndrew Truckle16-Apr-20 21:44 
GeneralExcellent!!! [modified] Pin
LupinTaiwan21-Nov-07 16:40
LupinTaiwan21-Nov-07 16:40 
GeneralRe: Excellent!!! Pin
Zuoliu Ding21-Nov-07 17:15
Zuoliu Ding21-Nov-07 17:15 
GeneralSame code in C# Pin
Zapss15-Jan-06 23:37
Zapss15-Jan-06 23:37 
GeneralRe: Same code in C# Pin
Zuoliu Ding28-May-07 15:26
Zuoliu Ding28-May-07 15:26 
Generalnot seeing line when first dropped down Pin
prosen11233422-Sep-04 9:35
prosen11233422-Sep-04 9:35 
GeneralRe: not seeing line when first dropped down Pin
prosen11233422-Sep-04 9:39
prosen11233422-Sep-04 9:39 
GeneralRe: not seeing line when first dropped down Pin
Zuoliu Ding2-Sep-04 10:40
Zuoliu Ding2-Sep-04 10:40 
GeneralRe: not seeing line when first dropped down Pin
prosen11233422-Sep-04 11:08
prosen11233422-Sep-04 11:08 
GeneralRe: not seeing line when first dropped down Pin
Zuoliu Ding2-Sep-04 11:21
Zuoliu Ding2-Sep-04 11:21 
GeneralRe: not seeing line when first dropped down Pin
prosen11233422-Sep-04 12:30
prosen11233422-Sep-04 12:30 
GeneralRe: not seeing line when first dropped down Pin
Zuoliu Ding2-Sep-04 13:09
Zuoliu Ding2-Sep-04 13:09 
GeneralRe: not seeing line when first dropped down Pin
Thomas Lins5-Feb-13 4:24
Thomas Lins5-Feb-13 4:24 
GeneralRe: not seeing line when first dropped down Pin
ramad14-Mar-13 4:27
ramad14-Mar-13 4:27 
GeneralRe: not seeing line when first dropped down Pin
Zuoliu Ding14-Mar-13 8:10
Zuoliu Ding14-Mar-13 8:10 
PraiseRe: not seeing line when first dropped down Pin
Andrew Truckle16-Apr-20 21:42
professionalAndrew Truckle16-Apr-20 21:42 
GeneralRe: not seeing line when first dropped down Pin
Ramesh Kommuri13-Feb-13 5:56
Ramesh Kommuri13-Feb-13 5:56 
GeneralRe: not seeing line when first dropped down Pin
Zuoliu Ding13-Feb-13 8:44
Zuoliu Ding13-Feb-13 8:44 
AnswerRe: not seeing line when first dropped down Pin
Andrew Truckle16-Apr-20 21:45
professionalAndrew Truckle16-Apr-20 21:45 
GeneralNot run(VisualStudio 6) Pin
albertTT5-Aug-04 15:39
albertTT5-Aug-04 15:39 
GeneralRe: Not run(VisualStudio 6) Pin
Zuoliu Ding6-Aug-04 9:03
Zuoliu Ding6-Aug-04 9:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.