在写这边文章前已经用过多次自绘按钮了。但此前的使用都没有对ODA和ODS进行详细的分析,都以第一次分析出来的粗糙结果为依据进行按钮的绘制。而这次呢,对整个WM_DRAWITEM中所涉及到的关于CButton自绘需要处理的ODA和ODS进行了不完整的分析(抛弃了两个变化中一直出现的值)。
一、DRAWITEMSTRUCT
要自绘控件,这个结构是不可少的。一下是MSDN关于这个结构的解释(VIsual Studio 2008 SP1离线版MSDN,虽然较老了,很多东西还是没变)。
<span style="white-space:pre"> </span>typedef struct tagDRAWITEMSTRUCT {
<span style="white-space:pre"> </span>UINT CtlType;
<span style="white-space:pre"> </span>UINT CtlID;
<span style="white-space:pre"> </span>UINT itemID;
<span style="white-space:pre"> </span>UINT itemAction;
<span style="white-space:pre"> </span>UINT itemState;
<span style="white-space:pre"> </span>HWND hwndItem;
<span style="white-space:pre"> </span>HDC hDC;
<span style="white-space:pre"> </span>RECT rcItem;
<span style="white-space:pre"> </span>ULONG_PTR itemData;
<span style="white-space:pre"> </span>} DRAWITEMSTRUCT;
成员:
CtlType 用于指明控件类型的无符号整型,其值如下之一:
ODT_BUTTON 自绘按钮
ODT_LISTVIEW 自绘List View
ODT_MENU 自绘菜单
ODT_TAB TAB控件
CtlID 控件ID
itemID 用于菜单项ID、LISTVIEW或Combo Box中Item的索引值
itemAction 自绘需要执行的动作,其值如下之一:
ODA_DRAWENTIRE 整个控件都需要绘制
ODA_FOCUS 控件获取了焦点或失去了焦点,控件是否具有焦点需要通过ODS_FOCUS来判定
ODA_SELECT 选择状态发生了改变,与ODA_FOCUS类似,也需要对ODS_SELECTED进行检测来判定。
itemState 当前需要绘制的可是状态,其值如下之一:
ODS_CHECKED 标志这菜单项将被勾选,仅用于菜单
ODS_COMBOBOXEDIT 绘制于自绘Combo Box的Edit控件
ODS_DEFAULT 需要绘制的Item为默认Item
ODS_DISABLED 该项需要被绘制为禁用状态
ODS_FOCUS 该项具有焦点(MSDN原文“The item has the keyboard focus.”)
ODS_GRAYED 该项需要灰显,仅用于菜单
ODS_SELECTED 菜单项被选中
hwndItem 控件句柄(对于菜单,为包含了该菜单项的一个句柄)
hDC 设备上下文
rcItem 包含了需要绘制部分的矩形
itemData 对于菜单,其指向一个与菜单项关联的无符号长整型;对于其他控件,查看该控件与绑定数据有关的部分,如LB_SETITEMDATA(按钮控件为0)。
二、CButton自绘itemAction与itemState的值分析
在测试时,我将ItemAction与itemState(简称Action和State)与其预定值输出到Visual Studio 2013的输出栏中。
在测试过程中使用了两种操作方式对自绘按钮进行了操作,观察Action和State的值变化在两种操作方式下是否不同:正常操作式的在按钮上单击鼠标左键(简称正常操作)和傻瓜式的在按钮上按下鼠标左键并将鼠标移动到鼠标外再松开鼠标左键(简称非常操作,我是有这样操作过按钮玩- -!所以测一下是不是有不同)。
以下是我获取的测试值(仅列出非0信息):
1.正常操作
(1) ODA_FOCUS 4
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
(后两个值在上述版本MSDN中并未阐明,但从命名不难理解其含义)
(2) ODA_DRAWENTIRE 1
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
ODS_SELECTED 1
(3) ODA_DRAWENTIRE 1
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
(4) ODA_DRAWENTIRE 1
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
2.非常操作
(1) ODA_FOCUS 4
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
(2) ODA_DRAWENTIRE 1
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
ODS_SELECTED 1
(3) ODA_DRAWENTIRE 1
ODS_FOCUS 16
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
(4) ODA_DRAWENTIRE 1
ODS_NOACCEL 256
ODS_NOFOCUSRECT 512
从两组值可以看出来两种操作方式值的变化是相同的,并且ODS_NOACCEL与ODS_NOFOCUSRECT在每次变化中均存在,也就是说这两个值对于自绘按钮区分需要绘制的状态(正常状态、具有焦点、被按下、恢复正常)没有什么帮助,那么数据可以精简如下:
(关于按像Viusal Studio这样的UI中鼠标悬浮其上控件会高亮,这中状态并不能在WM_DRAWITEM中处理。因为你在测试可以发现你悬浮鼠标并不会收到任何消息,可以用WM_MOUSEHOVER处理)
(1) ODA_FOCUS 4
ODS_FOCUS 16
(2) ODA_DRAWENTIRE 1
ODS_FOCUS 16
ODS_SELECTED 1
(3) ODA_DRAWENTIRE 1
ODS_FOCUS 16
(4) ODA_DRAWENTIRE 1
四组值也就是说在整个单击事件中WM_DRAWITEM消息处理函数被调用了四次。
1、第一组值:ODA_FOCUS和ODS_FOCUS 标志这控件具有了焦点,需要进行具有焦点的状态绘制。
MSDN中说了ODA_FOCUS置位表示控件拥有了或者失去了焦点,而是否是具有了焦点以ODS_FOCUS是否置位进行判断。这里两者都被置位,那么即是控件具有了焦点。
2、第二组值:ODA_DRAWENTIRE、ODS_FOCUS、ODS_SELECTED 表示控件需要进行完整的绘制、焦点绘制、以及被选中状态(后议,由于我参考的是较老的MSDN版本,其中对这个值有两种不同的解释:ODS_SELECTED This bit is set if the item's status is selected.和 ODS_SELECTED The status of the menu item is selected.一个并未言明是用于菜单,一个却指明了用于菜单。)
3、第三组值:ODA_DRAWENTIRE 和 ODS_FOCUS 表示控件需要完整绘制并且需要进行焦点状态绘制
4、第四组值:ODA_DRAWENTIRE 表示控件需要完整的绘制,当然单击事件结束控件需要恢复正常状态。
在1、2、3组值表明了在进行恢复正常状态绘制前,控件都具有焦点,也就是说自绘按钮在这个过程中也应该进行相应的焦点状态绘制处理,如绘制焦点边框(CDC中有这个成员函数)。而2、3、4组值表明了再除了第一次绘制焦点状态外,每次变化都需要重新绘制整个按钮。然后再绘制响应的状态。
根据上面对MSDN阐述不清进行讨论:
I 若ODS_SELECTED是仅用于菜单,那么第2、3组值可以合并。也就是说整个过程中需要的操作是:
正常+焦点 -> 正常+焦点 -> 正常
(因为自绘控件的第一次自绘就是ODA_DRAWENTIRE,所以一开始就是正常状态。再加上第一次的焦点绘制,那么就成了具有焦点的正常状态。如:在正常状态按钮下,进行了绘制焦点边框,那么按钮的可视状态就是具有焦点边框的样子)
前俩过程重复了。设计者必然是考虑了效率的,重复的操作也是不需要的。
既然测试中出现了四组值,对WM_DRAWITEM消息处理函数进行了四次调用,那么这四个状态必然还是有所不同的,ODS_SELECTED也就是有意义的。
II 若ODS_SELECTED不仅用于Menu,那么需要的操作如下:
正常+焦点 -> 正常+焦点+被选中 -> 正常+焦点 -> 正常
这个过程更像是 鼠标刚按下时,按钮便具有了焦点,为按钮添加了焦点状态绘制 -> 然后按钮被按下了,当然也该具有焦点 -> 开始恢复,在恢复时仍具有焦点 ->到完全恢复正常,失去了焦点。
以上就是对此的分析。至于ODS_SELECTED在此处是否有意义需要去新版的MSDN以及实践验证。不过我认为第二种过程更符合实际一些。
三、自绘
通过上面的分析可以看出进行自绘需要具有如下功能的代码:
1、绘制正常状态的按钮
2、绘制具有焦点状态的按钮
3、绘制被按下状态且具有焦点状态的按钮
当然具体的看设计者自己怎么组合上述测试的值了,只有正常与被按下两种状态也行。