深度理解:OwnerDraw和CustomDraw

问题

如果需要自定义控件的呈现效果,我们会使用两种不同的实现手法:OwnerDraw和CustomDraw。今天就来具体讲一讲它们的区别。

OwnerDraw

所谓OwnerDraw,这里的”Owner”可以理解为控件的父窗口,也可以是控件类的派生类。当控件的OwnerDraw属性被启用时,控件的父窗口负责控件的所有绘制工作。

如何启用控件的OwnerDraw属性

对于普通按钮,我们可以加上BS_OWNERDRAW属性。

对于ListBox控件,我们可以加上LBS_OWNERDRAWFIXED或者LBS_OWNERDRAWVARIABLE属性。

对于ListView控件,我们可以加上LVS_OWNERDRAWFIXED属性。

对于ComboBox控件,我们可以加上CBS_OWNERDRAWFIXED或者CBS_OWNERDRAWVARIABLE属性。

对于CStatic控件,我们可以加上SS_OWNERDRAW属性。

这里的XXXFIXED和XXXVARIABLE主要用在有列表项目的控件中,它们都是表明父窗口负责绘制控件,不同的是,XXXFIXED表明列表中的项目的高度都是一样的,而XXXVARIABLE表明项目高度可以不一样,是可变的。

OwnerDraw绘制原理

当OwnerDraw属性被启用,每当控件需要绘制的时候,系统会发送WM_DRAWITEM消息给父窗口。当我们不想在父窗口中处理这个消息时,我们可以从控件类中创建一个派上类,并在派上类中使用反射或者重写DrawItem虚函数实现WM_DRAWITEM消息的处理。

需要注意的是:如果控件的OwnerDraw属性没有被开启,则系统是不会发送WM_DRAWITEM消息的。

以下是我们通过重写DrawItem虚函数实现一个自定义按钮的例子,在这里,我们从CButton类派生出CMyButton类,并重写其DrawItem虚函数。

16868175-efbf2a766c4ce1a8

再次强调一下,我们需要先在资源管理器中启用按钮的OwnerDraw属性,DrawItem虚函数才能被调用。

CustomDraw

CustomDraw没有对应的属性可供开启。每当控件需要绘制的时候,系统都会发送NM_CUSTOMDRAW通知(借由WM_NOTIFY消息)给父窗口。控件的父窗口或者派生类可以选择处理或不处理此通知,如果处理了,则我们可以认为这个控件使用了CustomDraw机制。

CustomDraw绘制原理

在CustomDraw场景下,控件的大部分绘制工作还是由系统默认完成,我们只是在系统绘制之前或者之后对控件的呈现进行某种方式的”微调”。比如,我们可以使用CustomDraw机制来修改ListView控件中的项目背景色,文字的前景或背景色等。

在控件绘制的整个阶段中,系统划分了一系列不同的阶段,开发者设置了感兴趣的阶段后,系统就会在每个开发者感兴趣的阶段发送NM_CUSTOMDRAW通知。当我们收到NM_CUSTOMDRAW通知的时候,我们可以根据通知中的DrawStage知道当前绘制的阶段。

这里的感兴趣的阶段,可以这样理解:如果希望在控件绘制的各个阶段都收到NM_CUSTOMDRAW通知,我们需要手动设置NM_CUSTOMDRAW消息处理例程的第二个参数(pResult)。如果不对这个参数进行设置,则只会在CDDS_PREPAINT阶段收到NM_CUSTOMDRAW消息,其他所有绘制阶段将不会收到此消息。

可供使用的NM_CUSTOMDRAW返回标志

CDRF_DEFAULT Indicates that the control is to draw itself. This value—which should not be combined with any other value—is the default value.

CDRF_SKIPDEFAULT Used to specify that the control is not to do any drawing at all.

CDRF_NEWFONT Used if your code changes the font of an item/subitem being drawn.

CDRF_NOTIFYPOSTPAINT Results in notification messages being sent after the control or each item/subitem is drawn.

CDRF_NOTIFYITEMDRAW Indicates that an item (or subitem) is about to be drawn. Note that the underlying value for this is the same as CDRF_NOTIFYSUBITEMDRAW.

CDRF_NOTIFYSUBITEMDRAW Indicates that a subitem (or item) is about to be drawn. Note that the underlying value for this is the same as CDRF_NOTIFYITEMDRAW.

CDRF_NOTIFYPOSTERASE Used if your code needs to be notified after the control has been erased.

例如,我们希望ListView控件在绘制它的项目及子项目的时候能收到NM_CUSTOMDRAW消息,则我们需要在NM_CUSTOMDRAW处理例程中指定pResult。

16868175-9ed14288cf168841

当我们通过pResult设置我们感兴趣的绘制阶段后,我们可以通过在不同阶段编写不同的绘制代码,实现控件的自定义呈现效果。

控件绘制的不同阶段

CDDS_PREPAINT

CDDS_ITEM

CDDS_ITEMPREPAINT

CDDS_ITEMPOSTPAINT

CDDS_ITEMPREERASE

CDDS_ITEMPOSTERASE

CDDS_SUBITEM

CDDS_POSTPAINT

CDDS_PREERASE

CDDS_POSTERASE

以下是一个CustomDraw的例子:

16868175-33075c911cf4b4c0

在以上代码中,我们重写了控件的OnCustomDraw方法,当收到NM_CUSTOMDRAW消息的时候,此方法会被调用。我们使用了一个switch结构来对不同的绘制阶段进行处理。

例如,当我们想在项目绘制之前自定义文字的前景和背景色,我们可以在CDDS_ITEMPREPAINT和CDDS_SUBITEM阶段加入我们的绘制代码:

16868175-d70be09fac108a72

总结

1) OwnerDraw绘制机制需要开启控件对应的OWNERDRAW属性才能使用。

2) OwnerDraw需要”owner”负责控件的所有绘制工作,所以,这种方法可以适用于所有Windows界面控件,而CustomDraw只能适用于Common Controls。

3) 由于CustomDraw是在控件绘制的各个阶段加入我们自己的绘制代码,在大部分时候,控件的绘制还是有系统完成,所以,CustomDraw可以看做是一款”轻量级”OwnerDraw。

猜你喜欢

转载自blog.csdn.net/weixin_34307464/article/details/90850658