Click here to Skip to main content
6,594,932 members and growing! (15,613 online)
Email Password   helpLost your password?
Desktop Development » Combo & List Boxes » ComboBox Controls     Intermediate License: The Code Project Open License (CPOL)

A separator combo-box

By Zuoliu Ding

A CComboBox derived combo-box class
VC7, VC7.1, Windows, MFC, Dev
Posted:8 Jun 2004
Updated:13 Jun 2004
Views:79,661
Bookmarked:30 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
14 votes for this article.
Popularity: 4.85 Rating: 4.23 out of 5
1 vote, 7.1%
1
1 vote, 7.1%
2

3
2 votes, 14.3%
4
10 votes, 71.4%
5

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

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


Member
A Software Engineer and Adjunct Faculty in Los Angeles and Orange County
Occupation: Software Developer
Location: United States United States

Other popular Combo & List Boxes articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 17 of 17 (Total in Forum: 17) (Refresh)FirstPrevNext
GeneralExcellent!!! [modified] PinmemberLupinTaiwan17:40 21 Nov '07  
GeneralRe: Excellent!!! PinmemberZuoliu Ding18:15 21 Nov '07  
GeneralSame code in C# PinmemberZapss0:37 16 Jan '06  
GeneralRe: Same code in C# PinmemberZuoliu Ding16:26 28 May '07  
Generalnot seeing line when first dropped down Pinmemberprosen112334210:35 2 Sep '04  
GeneralRe: not seeing line when first dropped down Pinmemberprosen112334210:39 2 Sep '04  
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding11:40 2 Sep '04  
GeneralRe: not seeing line when first dropped down Pinmemberprosen112334212:08 2 Sep '04  
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding12:21 2 Sep '04  
GeneralRe: not seeing line when first dropped down Pinmemberprosen112334213:30 2 Sep '04  
GeneralRe: not seeing line when first dropped down PinmemberZuoliu Ding14:09 2 Sep '04  
GeneralNot run(VisualStudio 6) PinmemberalbertTT16:39 5 Aug '04  
GeneralRe: Not run(VisualStudio 6) PinmemberZuoliu Ding10:03 6 Aug '04  
GeneralRe: I can not connect url link. PinmemberalbertTT15:36 6 Aug '04  
GeneralRe: I can not connect url link. PinmemberZuoliu Ding18:07 6 Aug '04  
GeneralRe: I can not connect url link. PinmemberalbertTT20:49 6 Aug '04  
GeneralRe: I can not connect url link. PinmemberBruno Norberto2:46 3 Dec '04  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 13 Jun 2004
Editor: Nishant Sivakumar
Copyright 2004 by Zuoliu Ding
Everything else Copyright © CodeProject, 1999-2009
Web21 | Advertise on the Code Project