Generic Picker Dropdown Control





5.00/5 (12 votes)
A flexible easily overridable combo-box-alike control for choosing from a 2D array of options

Introduction
This article was inspired by Chris Maunder's Office 97 style Colour Picker control. A couple of years ago, I modified it greatly to pick from a palette of colours in my software. Rather than creating my own palette, I used the definitions that Autocad does in DXF files. 0 = black, 1 = white, 2 red, etc. More recently, I needed to add some drawing facilities. Once I added a couple of patterns, I wanted to add the rest. Which makes for a bust 60odd drop down list. Similarly, I wanted drop downs for line width and line styles.
So I went back to Chris' article, stole/re-used the parts dealing with the popup window, and made the rest. As I knew, I would be making a variety of related controls, I made some member functions pure virtual, and implemented the specific functionality in several inheriting classes.
Lastly, the example project, and most of the pre-built pickers use GDI+. That's not at all related to the core idea - but it was the raison d'etre (sultana of summer, from the French) of the project.
The Pre-built Pickers
Colour Picker

This is the original one I implemented, and most closely modelled on Chris' implementation.
Line Style

Line Width

Hatch Style

How to Use a Generic Picker Control in Your Own Code.
This is hopefully pretty simple! I'll be using the Line Style Picker as my example. First, add a button to your dialog, and give it an ID number. The text is irrelevant, but nice for editing the dialog later.

Next, include the header file and member variable to your dialog box:
#include "DrawingPickers.h"
...
class CGenericPickerDemoDlg : public CDialog
{
...
CPickerLineStyle m_LineStyle;
...
};
Next, subclass the control in your OnInitDialog
dialog member function. I also like to initialise the current item. Feel free to use DDX_Control
in the DoDataExchange
if you wish - but I rarely do.
BOOL CGenericPickerDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
...
m_LineStyle.SubclassDlgItem (IDC_LINESTYLE, this);
m_LineStyle.SetStyle (Gdiplus::DashStyleSolid);
...
return TRUE; // return TRUE unless you set the focus to a control
}
To handle changes in selection, you should handle the CBN_SELCHANGE
notification. Chris' control used a custom message, but I wanted to be lazy and be able to use ON_CBN_SELCHANGE
macro for my code.
BEGIN_MESSAGE_MAP(CGenericPickerDemoDlg, CDialog)
...
ON_CBN_SELCHANGE(IDC_LINESTYLE, OnPickerChanged)
...
END_MESSAGE_MAP()
Lastly, you need to do something with the selection. In my demo program, I draw a circle with the various styles. As this is not a GDI+ tutorial, I'll skip over that part!
Making Your Own Picker
There are a few pure functions in CGenericPicker
you need to override. Here they are:
virtual int GetColumns () const = 0;
virtual int GetRows () const = 0;
virtual void MeasureSubItem(CSize &mis) = 0;
virtual void DrawSubItem(const DrawItemSubStruct &dis) = 0;
virtual BOOL ShowDefaultItem () const = 0;
virtual BOOL IsCellValid (int nCol, int nRow) const { return TRUE; }
I'll show the CPickerLineWidth
's implementation as an example.
GetColumns & GetRows
int CPickerLineWidth::GetColumns () const { return 2; }
int CPickerLineWidth::GetRows () const { return 10; }
Hopefully these two overridables are fairly self-explanatory! How many boxes wide and high is the 2D drop down...
MeasureSubItem
void CPickerLineWidth::MeasureSubItem(CSize &mis)
{
mis.cx /= 2;
mis.cy = 12;
}
MeasureSubItem
is called once. The CSize
structure is initialised to the same size as the button you added to your dialog. This makes it easy to match the width of the drop down to the width of the parent control. As I have two columns, and neither need to be a specific width, I just split the width between them. For other controls (like colour, or hatch style, this is made to be a fixed size.
DrawSubItem
void CPickerLineWidth::DrawSubItem(const DrawItemSubStruct &dis)
{
CRect rc (dis.rcItem);
Graphics graphics (*dis.pDC);
Color colour;
colour.SetFromCOLORREF(dis.bSelected ? GetSysColor (COLOR_BTNTEXT) :
GetSysColor (COLOR_BTNSHADOW));
float fWidth = dis.nRow + float(10 * dis.nColumn);
fWidth /= 2.0f;
Pen pen (colour, fWidth);
graphics.DrawLine (&pen, rc.left, rc.top + 6, rc.right, rc.top + 6);
}
DrawSubItem
does the heavy lifting of the control. It is used for drawing the button, and for drawing each cell. The DrawItemSubStruct
structure has the CDC, drawing rectangle, current column and row, and whether it should be drawn as selected.
The selected option is used to choose between drawing in dark grey or black in my controls. Feel free to be fancier.
ShowDefaultItem
BOOL CPickerLineWidth::ShowDefaultItem () const { return FALSE; }
This isn't really implemented by me - Chris' control uses it. This lets the inheriting controls tell the CGenericPicker
class know whether to leave room for a default item. This will be drawn with a column and row of -1.
Acknowledgements
- Hard to ignore the influence that Chris's control had on this code - Office 97 style Colour Picker control
- I also use Keith Rules
CMemDC
class. In fact, I use it in almost all the drawing code I ever do - Flicker Free Drawing In MFC
History
- 1.0 Initial release