Click here to Skip to main content
Click here to Skip to main content

A separator combo-box

, 13 Jun 2004
Rate this:
Please Sign up or sign in to vote.
A CComboBox derived combo-box class

Introduction

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

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,

   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:

sepcmb2.jpg

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

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:

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);
} </p>
 

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,his,

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

Class Interface

The following is the CSeparatorComboBox class.

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.

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, no change the item height.

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

   m_arySeparators.Add(iSep);
}

The rest 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

CSeparatorComboBox m_ctrlCombo;

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

   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

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

Optionally, set any attributes you want, such as:

 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, may try that idea – no change the current text item height but make room for a separator similar to an item. Override the virtual functionor <code>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 and worthwhile..

License

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

About the Author

Zuoliu Ding
Software Developer
United States United States
An Adjunct Faculty and Software Developer in Los Angeles and Orange County, CA
 
* Typical articles published in Dr. Dobb’s Journal and Windows Developer Magazine:

- A Silent Component Update for Internet Explorer
- Silent Application Update
- An MDI-Style Web Browser and Load Spy Monitor
- Implementing Wireless Print for WinNT/Win2K
- Multi-State Checkbox Tree Views
- A Generic Tool Tip Class
- An Easy Way to Add Tool Tips to Any MFC Control

- More from Google...

Comments and Discussions

 
QuestionWindows 7: Seperator isn't drawn properly PinmemberNaryoril30-Mar-10 4:48 
GeneralExcellent!!! [modified] PinmemberLupinTaiwan21-Nov-07 16:40 
GeneralRe: Excellent!!! PinmemberZuoliu Ding21-Nov-07 17:15 
GeneralSame code in C# PinmemberZapss15-Jan-06 23:37 
GeneralRe: Same code in C# PinmemberZuoliu Ding28-May-07 15:26 
Generalnot seeing line when first dropped down Pinmemberprosen11233422-Sep-04 9:35 
GeneralRe: not seeing line when first dropped down Pinmemberprosen11233422-Sep-04 9:39 
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding2-Sep-04 10:40 
GeneralRe: not seeing line when first dropped down Pinmemberprosen11233422-Sep-04 11:08 
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding2-Sep-04 11:21 
GeneralRe: not seeing line when first dropped down Pinmemberprosen11233422-Sep-04 12:30 
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding2-Sep-04 13:09 
GeneralRe: not seeing line when first dropped down PinmemberThomas Lins5-Feb-13 4:24 
GeneralRe: not seeing line when first dropped down Pinmemberramad14-Mar-13 4:27 
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding14-Mar-13 8:10 
GeneralRe: not seeing line when first dropped down PinmemberRamesh Kommuri13-Feb-13 5:56 
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding13-Feb-13 8:44 
GeneralNot run(VisualStudio 6) PinmemberalbertTT5-Aug-04 15:39 
GeneralRe: Not run(VisualStudio 6) PinmemberZuoliu Ding6-Aug-04 9:03 
GeneralRe: I can not connect url link. PinmemberalbertTT6-Aug-04 14:36 
GeneralRe: I can not connect url link. PinmemberZuoliu Ding6-Aug-04 17:07 
GeneralRe: I can not connect url link. PinmemberalbertTT6-Aug-04 19:49 
GeneralRe: I can not connect url link. PinmemberBruno Norberto3-Dec-04 1:46 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 14 Jun 2004
Article Copyright 2004 by Zuoliu Ding
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid