正文

Custom Draw ListView Controls(1)2009-01-20 11:35:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/yuqiexing/40618.html

分享到:

Custom Draw ListView Controls
The first is the most extreme. You handle the WM_PAINT messages and do all the painting yourself. You get no help at all from Windows with this method. You have to create a device context, determine where and how big your control is, what state it is in, and then draw it all yourself. Most programmers tend to shy away from this.

Owner-draw (or self-draw) controls are a little easier. Here, Windows sets up the device context for you. It also fills in a structure that gives you the rectangle that the control occupies, the state the control is in, and flags to say how much drawing you need to do. You still need to do all the drawing yourself, but with all this information handed to you, it is not such a chore. In particular, for controls like list boxes and ListView controls, Windows will go line by line through the control and ask you to draw each individual item; you don't need to know how to draw the entire control. To make things easy for owner-draw control writers, Windows also provides special drawing functions, like DrawFrameControl. Owner-draw is available for static, button, combo box and list box controls, but not for edit controls. It is also available for the ListView and Tab common controls. However, for ListView controls, owner draw only works in report mode and you have to draw the entire item (including all the sub items) in one go.

For Windows standard controls (buttons, edit boxes etc), one can also use the WM_CTLCOLOR family of notification messages to make some simple changes, usually just to the colours used. Windows sends WM_CTLCOLOR messages during the actually painting cycle of the control to give you the chance to make changes to the device context and nominate a brush to use for the background. You are somewhat limited in what you can do here, but the big advantage is that the control still does most of the work itself; you do not need to tell it how to draw itself. However, these messages are only available for the standard Windows controls; the Window Common controls do not support WM_CTLCOLOR.

Custom Draw

Windows common controls add yet another method of customising called custom draw. This is different to owner draw, although some programmers confuse the two. It is available for ListView, TreeView, ToolTip, Header, TrackBar, Toolbar and Rebar controls.

Custom draw lets you hook into the paint cycle of the control in one or more stages of the drawing process. At each draw stage you can make changes to the drawing context and choose to either do your own drawing, let the control do the drawing, or combine the two. You also get to control what further notifications you will receive during the paint cycle.

NM_CUSTOMDRAW Notifications

When the control first starts to paint itself, in response to a WM_PAINT, you receive a NM_CUSTOMDRAW notification message, with the draw stage set to CDDS_PREPAINT. If you don't handle this yourself that will be the end of it, as the default message handler will just tell the control to carry on with default drawing and not to interrupt you again.

If you do handle the message, you have a chance to do a bit of painting yourself if you want. You can then set a flag in the return result that says whether you want the control to do its default painting; you usually will. You can also set flags to say whether you want to receive further notifications. You can ask the control to send you a WM_CUSTOMDRAW notification for the CDDS_POSTPAINT draw stage when the control has finished drawing (you can then do some extra drawing yourself). You can also say that you want to get notifications for the CDDS_ITEMPREPAINT draw stage for each item drawn.

Similarly, when you get each WM_CUSTOMDRAW notification for the CDDS_ITEMPREPAINT draw stage, you can set up the colours to use, make changes to the device context including font changes, and maybe do some drawing yourself. You can then say whether you want the default painting for the item and whether you want to receive a CDDS_ITEMPOSTPAINT when the item drawing is finished.

If you are in report mode, you can also ask for individual notifications for each subitem. These have the draw stage set to (CDDS_ITEMPREPAINT | CDDS_SUBITEM), and again you can fiddle with the device context, do your own drawing and/or let the control do some drawing and optionally receive a (CDDS_ITEMPOSTPAINT | CDDS_SUBITEM) drawing stage message at the end of each subitem.

NOTE: subitem notifications are only available for custom controls V4.71 (that is, for IE4.0 or Windows 98) or later, so please ensure that you check the version of COMCTL32 you have on your machine if you intend to use custom draw controls.

Here is a diagram showing the general flow of NM_CUSTOMDRAW notification messages for a list control with two items, each with two sub-items (columns):

Why Custom Draw

OK. That's a lot of notification messages. But what's in it for me? Well, lets compare the advatnages of custom draw over owner draw for ListView controls:

Owner Draw

Custom Draw

Only works with report-view style

Works for any style

Must do all the drawing yourself

Can choose you own drawing, default drawing of combinations of both. You can also change colours and fonts for default drawing.

Must draw the entire line (including subitems) in one go

Can handle sub items individually

The price one pays for this flexibility is some complexity in writing the handler for the NM_CUSTOMDRAW. All the various drawing stages come through the one handler. It has to understand how to behave for each of the draw stages, extract the required information from the NMLVCUSTOMDRAW struct, and set the correct flags so that it receives subsequent notification messages correctly.

But things are more complicated than this. Some of the information in the NMLVCUSTOMDRAW struct is only relevant during certain draw-stage notifications. For example, Windows only sets the iSubItem value for the sub items notifications; it has rubbish values for the other notifications message. Similarly, Windows only fills in the RECT in the struct with valid values for some draw stages (depending on the version of common controls you have). Experimental evidence shows that even this is not completely correct; in fact only some of the values are valid during some drawing stages and others are not.

To ease the burden on the working class programmer, I have written a class called CListCtrlWithCustomDraw that does a lot of the housekeeping work for you, and uses simple virtual functions that you can override to provide the functionality you require. You do not need to worry about setting the correct flags, or unpacking information from structures and so on. In my next article, I'll present not only this example code, but I'll also walk you through the specifics on how exactly it all works.

The next step is to add a handler for the NM_CUSTOMDRAW notification message. Usually, to add a handler, one can simply right-click on CListCtrlWithCustomDraw in the class view, or use the WizardBar, and "Add windows message handler." However, this time there is a catch. NM_CUSTOMDRAW is nowhere to be seen in the list of available messages.

Well, it looks like the wizard is not going to do the job this time. However, we can get it to help. I find the easiest way to add a handler is to pick a similar message and then edit the resultant code. Even though the Wizard did not know about the message in the first place, it will quite happily work with the modified handler that results. In this case, I used the NM_OUTOFMEMORY. However, in order to reduce the amount of editing needed, I changed the name of the handler function to "OnCustomdraw".

Now you can dive into the generated source code and manually edit the message map in the source file and change:

ON_NOTIFY_REFLECT(NM_OUTOFMEMORY, OnCustomdraw)

to read:

ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomdraw)

When you look at the resulting OnCustomdraw function there are two arguments: pNMHDR and pResult. pResult is where we will set the flags that indicate when we want further custom draw messages and whether or not to use the default painting. For a List View control, the pNMHDR is actually a pointer to a NMLVCUSTOMDRAW structure (notification message for ListView custom draw) that tells us about the current drawing stage, what item or subitems we are looking at, and so forth.

We could edit the function declaration so it passes a NMLVCUSTOMDRAW*, but instead we can safely cast the pNMHDR from NMHDR* to NMLVCUSTOMDRAW* to give access to the data, even though these are distinct and separate structures.

As an aside, those from a C++ background may ask why NMLVCUSTOMDRAW does not simply derive from NMHDR in the first place. Well, that sort of thing just does not happen in the Windows SDK API. Because Microsoft designed the SDK API to work with both C and C++, it cannot use any C++ specific language features. That means all the structures in the SDK are PODs (plain old data structures) with no member functions, inheritance, and so on.

But all is not lost.

To get a similar effect to inheritance the SDK uses a C technique; the first member of a 'derived' struct is an instance of the 'base' struct. In this particular case, we have (with simplified declarations):


typedef struct {

 HWND hwndFrom;

 UINT idFrom;

 UINT code;

} NMHDR;

typedef struct {

 NMHDR  hdr;

 DWORD  dwDrawStage;

} NMCUSTOMDRAW;

typedef struct {

 NMCUSTOMDRAW nmcd;

 COLORREF clrText;

} NMLVCUSTOMDRAW;


Because NMLVCUSTOMDRAW is a POD, a pointer to a NMLVCUSTOMDRAW is also a pointer to its first member (nmcd), in other words a pointer to a NMCUSTOMDRAW. This in turn is a pointer to the first member of NMCUSTOMDRAW (hdr), which is a NMHDR. Therefore, it is quite legitimate to pass a pointer to a NMCUSTOMDRAW structure using a pointer to a NMHDR and cast it as required.

Now, on with the show.

阅读(4095) | 评论(2)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册