Introduction
Owner drawing controls is a nice skill to have. Easy to master with immediate impressive results. Tuck it away carefully because if used correctly, it can make your applications look more professional without crossing over to look like you are just trying too hard and getting it all wrong. By learning to use it, you'll wield the broad sword that Microsoft Word has, but Notepad doesn't. Let's continue now.
This article is to get you to understand the basics quickly.
Background
An owner drawn listview is a normal report view list view that also generates 4 owner draw messages. It expects you to do the drawing.
WM_DRAWITEM
(Call for each item when it needs to be drawn)
WM_MEASUREITEM
(Requests the height of the row, called when resized)
WM_COMPAREITEM
(Used for sorting)
WM_DELETEITEM
(Called for each item when it gets deleted)
Using the code
The breakdown:
Using the WTL wizard, I created a dialog based project, nothing special. I added a new class derived from COwnerDrawnListViewCtrl
.
class CDemoOwnerDrawnListViewCtrl :
public COwnerDrawnListViewCtrl<CDemoOwnerDrawnListViewCtrl>
{
public:
BEGIN_MSG_MAP(CDemoOwnerDrawnListViewCtrl)
CHAIN_MSG_MAP(COwnerDrawnListViewCtrl<CDemoOwnerDrawnListViewCtrl>)
END_MSG_MAP()
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
};
For DrawItem
, the dc you do your drawing onto is in lpDrawItemStruct->hDC
, and the rect for the current item is in lpDrawItemStruct->rcItem
.
For MeasureItem
, all you need to do is set lpMeasureItemStruct->itemHeight
to the height you need.
Your control is done.
All the lines that I added to the Dialog class have "
" appended to them so you can do a Find in Files. The basics for using your control are:
You should be good now.
Details
COwnerDrawnTaskListView
is a very simple class with only 3 functions, which itself derives from CWindowImpl
and COwnerDraw
.
void ForceMeasureItemMessage();
void DeleteItem(LPDELETEITEMSTRUCT ) {};
void GetCellRect(int header_column, const CRect& item_rect, CRect& cell_rect);
Inside COwnerDrawnTaskListView
, we CHAIN_MSG_MAP_ALT(COwnerDraw<TBase>, 1)
because we are handling the reflected messages (OCM_XXXX
). If we were the parent then we would just CHAIN_MSG_MAP(COwnerDraw<TBase>)
.
ForceMeasureItemMessage()
is required to receive a WM_MEASUREITEM
message. The problem is that you can't subclass the window quick enough to receive it initially because it's sent before it's created. The only way you can catch it is if you .Create()
or .CreateEx()
it. In that case, you don't need ForceMeasureItemMessage()
. ForceMeasureItemMessage()
's magic is moving the control up a little which generates a WM_MEASUREITEM
message. You get another one when ForceMeasureItemMessage()
moves it back.
The empty DeleteItem()
function is required to fix an ambiguous access issue that happens with WTL. What happens, if you don't define it, is that OnDeleteItem()
will want to call DeleteItem()
which is ambiguous. You got WTL::CListViewCtrlT<ATL::CWindow>::DeleteItem()
and WTL::COwnerDraw<T>::DeleteItem()
. Since they are in different MI branches, they are out of each other's overloading scope. Our empty DeleteItem()
function (which you can hide with your function down the hierarchy) hides both functions resolving the ambiguity.
GetCellRect()
is a function that returns a CRect
of the "cell" or the subitem's coordinates within a row which simplifies the drawing.
History
Sunday, June 27, 2004 - Created.