Among other common Windows dialogs we can find Color Dialog Box that allows to pick a color for our purposes. It can simply be created by a call of ChooseColor WinAPI function or, if using MFC, with the help of CColorDialog class.
Also, the MFC versions shipped with Visual Studio 2008 or newer, include CMFCColorDialog class which shows a little bit more fancy color selection dialog.
However, let’s say we have to make our own custom color picker control (let’s say, looking similar tho one used in Photoshop). Beside making a custom or ActiveX control from scratch, a faster approach is to customize the Windows listview (“SysListView32“) control, kept in MFC by CListCtrl class. There are two ways to customize a listview control: making it custom-draw or making it owner-draw.
Custom Draw Color Picker Listview
First we have to handle NM_CUSTOMDRAW notification, sent via WM_NOTIFY.
In subitem pre-paint phase, we can indicate which color has to be used to paint whatever list subitem.
Example
void CColorListCtrl::_HandleSubitemPrePaint(LPNMLVCUSTOMDRAW pNMCD, LRESULT *pResult) { // Note: for a listview, nmcd.dwItemSpec keeps the item index const int nItem = (int)pNMCD->nmcd.dwItemSpec; const int nSubItem = pNMCD->iSubItem; const int nColor = nItem * m_nColCount + nSubItem; if(nColor < m_arrColors.GetSize()) { pNMCD->clrTextBk = m_arrColors[nColor]; // m_arrColors is an array of COLORREFs } // ... }
Finally, hadle NM_CLICK notification, get the color of the clicked item and send a user message to the parent.
Example
void CColorListCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult) { LVHITTESTINFO lvhti = {0}; lvhti.flags = LVHT_ONITEM; ::GetCursorPos(&lvhti.pt); ScreenToClient(&lvhti.pt); int nItem = this->SubItemHitTest(&lvhti); if(-1 != nItem) { const UINT nColor = lvhti.iItem * m_nColCount + lvhti.iSubItem; if(nColor < (UINT)m_arrColors.GetSize()) { // notify parent CWnd* pWndParent = GetParent(); if(NULL != pWndParent) { pWndParent->SendMessage(CColorListCtrl::WM_COLORPICK, (WPARAM)m_arrColors[nColor]); } } } *pResult = 0; }
This method is pretty simple but it has a disadvantage: we cannot modify item/subitem height. So, if this is necessary we gave to make an owner-draw listview control.
Owner Draw Color Picker Listview
An owner-draw listview control must have LVS_OWNERDRAWFIXED style set (“Owner Draw Fixed” property must be set to “True” in the resource editor).
Next, we can handle WM_MEASUREITEM to set item size and override CListCtrl::DrawItem in order to perform custom drawing. Just to note: would be necessary to map by hand the WM_MEASUREITEM message; also DrawItem isn’t called for each subitem.
Example
void CColorListCtrl::MeasureItem(LPMEASUREITEMSTRUCT lpMIS) { lpMIS->itemHeight = m_nColorItemHeight; lpMIS->itemWidth = m_nColorItemWidth * m_nColCount; }
void CColorListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDIS) { CDC* pDC = CDC::FromHandle(lpDIS->hDC); const UINT nColorCount = (UINT)m_arrColors.GetSize(); for(UINT nColor = 0; nColor < nColorCount; nColor++) { const UINT nItem = nColor / m_nColCount; if(nItem == lpDIS->itemID) { UINT nSubItem = nColor % m_nColCount; CRect rcColor(0, 0, 0, 0); rcColor.top = lpDIS->rcItem.top; rcColor.bottom = lpDIS->rcItem.bottom; rcColor.left = lpDIS->rcItem.left + nSubItem * m_nColorItemWidth; rcColor.right = rcColor.left + m_nColorItemWidth; pDC->FillSolidRect(rcColor, m_arrColors[nColor]); } } }
Notes
- Implementation details can be found in the attached demo projects
- The color pickers presented here are just draft versions intended to demonstrate how to do. You can further improve them by adding new features like loading a custom color palette, adding custom colors on the fly, and so on.
- A brief presentation of custom-draw and owner draw controls, can be found in this article: Custom Draw vs. Owner Draw Controls.
Downloads
- Demo project: Color Picker Listview (custom-draw) (70)
- Demo project: Color Picker Listview (owner-draw) (69)