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