自绘控件的4种方法

在您决定开发 Windows 提供的常规免费自定义控件范围之外的控件之后,您必需确定自己的控件将有多少独到之处 — 在功能和外观两方面。例如,我们假定您正在创建一个类似于计速表的控件。由于公共控件库 (ComCtrl32.dll) 中没有类似的控件,您完全需要自己进行以下操作:编写所有控件功能需要的代码,进行绘制,默认终端用户的交互,以及控件与其父窗口之间需要的任意消息处理

(#add 两方面,公共控件库中没有类似的 完全重写;  只想调整公共控件功能,则可以部分修改)

  另一方面,还包括一些您只想调整公共控件功能的情况。例如,我们假定您想创建一个屏蔽编辑控件,它只允许接受指定的字符。如果使用 MFC,通常涉及从 MFC 提供的类派生一个类,该类封装了一个公共控件(在屏蔽编辑控件中,通常为 CEdit),重写必需的虚函数(或处理指定的消息),然后加入自定义的代码。

  本文讨论的重点介于两者之间 — 公共控件赋予您想要的大部分功能,但控件的外观并不是您想要的。例如,列表视图控件提供在许多视图风格中显示数据列表的方式 — 小图标、大图标、列表和详细列表(报告)。然而,如果您想要一个网格控件,那结果怎样呢?尽管公共控件库里没有特别包含网格,但是列表视图控件与它较为接近,它以行和列显示数据,并有一个相关的标头控件。因此,许多人以一个标准的列表视图控件为起点创建自己的网格控件,然后重写该控件及其子项的呈现方式或绘制方式。

   即使“只”进行绘制,您仍然有至少四种选项可用,它们都具有鲜明的优缺点:

·处理 WM_PAINT
·所有者绘制(owner draw)
·自定义绘制(custom draw)
·处理 WM_CTLCOLOR

 

  处理 WM_PAINT

  最极端的选择是执行一个 WM_PAINT 处理程序,并且自己完成所有的绘制。这意味着,您的代码将需要进行一些与呈现控件相关的琐事 — 创建适当的设备上下文(一个或多个),决定控件的大小和位置,绘制控件等。在绘制过程中,很少需要这种级别的控件。

  所有者绘制

  控制控件绘制的另一种方法是利用所有者绘制。事实上,您也许听开发人员提到过所有者绘制控件,因为它是用于开发自定义控件最普通的技术。该技术普遍使用的主要原因在于,Windows 可为您提供很多帮助。在呈现控件的那一刻,Windows 就已经创建并填写了设备上下文,决定了控件的大小和位置,并且向您传递信息以使您了解此刻绘制的需求。对于列表控件(例如,列表框和列表视图),Windows 将为列表中的每一项调用绘制代码,这意味着您只需绘制这些项,而无需考虑控件的其他方面。注意,所有者绘制可用于大多数控件。然而,它不能用于编辑控件;并且考虑到列表控件,它只能用于报表视图样式

  自定义绘制

  对于绘制自己的控件而言,这可能是最少为人所知的技术。事实上,许多技术能力较高的开发人员也混淆了术语所有者绘制 (owner-draw) 和自定义绘制 (custom-draw)。关于自定义控件,首先需要了解,它仅针对于指定的公共控件:标头、列表视图、rebar、工具栏、工具提示、跟踪条和树视图。此外,尽管所有者绘制只允许绘制报告视图风格的列表视图控件,而自定义绘制则使您能够处理列表视图控件所有视图风格的绘制。使用自定义绘制的另一个明显优势是,您可以对希望绘制的内容进行严格挑选。实现方式是,在控件绘制的每个阶段由 Windows 向代码发送一个消息。这样,您可以决定在每个阶段是自己进行所有的绘制工作,增加默认的绘制,还是允许 Windows 为该阶段执行所有的绘制。(鉴于自定义绘制是本文的一个主题,因此您很快会看到它的工作方式。)

  处理 WM_CTLCOLOR消息

这可能是帮助决定如何呈现控件最简单的方式。正如消息名所指,当要绘制一个控件,并且它能让您的代码决定要使用的画笔时,发送 WM_CTLCOLOR 消息(#add 似乎不对,应该用消息反射)。通常情况下,如果您只想更改控件的颜色(#addSetTextColor SetBkColor),并且不提供除控件本身之外的更多功能,则使用该技术。此外,对于由 Internet Explorer 引入的公共控件(列表视图、树视图、rebar 等),不发送该消息,并且它只与标准控件(编辑、列表框等)协同使用。

CTLCOLOR_STATIC               Static control

CTLCOLOR_BTN                     Button control

CTLCOLOR_EDIT                    Edit control

CTLCOLOR_LISTBOX             List-box control

CTLCOLOR_SCROLLBAR    Scroll-bar control

CTLCOLOR_DLG                    Dialog box

CTLCOLOR_MSGBOX            Message box

不会为组合框中的下拉列表框调用OnCtlColor函数,因为下拉列表框实际上是组合框的子窗口,而不是窗口的子窗口。要改变下拉列表框的颜色,创建一个CComboBox,在重载的OnCtlColor中的nCtlColor参数中检查CTLCOLOR_LISTBOX。在这个处理函数中,为设置文本的背景必须使用SetBkColor成员函数。

自定义绘制:

  既然您已经了解了绘制控件可用的各种选项(包括使用自定义绘制的好处),那么,让我们来看看实现一个自定义绘制控件需要的三个主要步骤。

·执行一个 NM_CUSTOMDRAW 消息处理程序。
·指定处理所需的绘制阶段。
·筛选特定的绘制阶段(在这些阶段中,您需要加入自己的特定于控件的绘制代码)。

 

  1,执行一个NM_CUSTOMDRAW 消息处理程序

  当需要绘制一个公共控件时,MFC 会将控件的自定义绘制通知消息(最初发送到控件的父窗口)以 NM_CUSTOMDRAW 消息的形式反馈给控件。以下是一个 NM_CUSTOMDRAW 处理程序的示例。

void CMyCustomDrawControl::OnCustomDraw(NMHDR* pNMHDR, LRESULT*pResult)
{
 LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
 ...
}

  正如您所见,NM_CUSTOMDRAW 处理程序将一个指针传递给 NMHDR 类型的结构。然而,该值不足以用于象 NMHDR 这样只包含三个成员(hwndFrom、idFrom 和 code)的结构。

  因此,您通常需要将该结构指针转换为信息量更大的结构 — LPNMCUSTOMDRAW。LPNMCUSTOMDRAW指向 NMCUSTOMDRAW,它包含诸如 dwDrawStage、dwItemSpec 和 uItemState 这样的成员 — 它们是决定当前绘制阶段及确切绘制(例如,控件本身、或控件的一个项目或子项)所必需的。

  这里值得注意的是,还可以将 NMHDR 指针指向特定于正在绘制控件的类型的结构。表 1 显示控件的一个列表及其相关的自定义绘制结构类型名。

  表 1:控件及其相关的自定义绘制结构

控件

结构(在 commctrl.h 中定义)

Rebar、Trackbar、AuthTicket、My.Resources、My.Settings、My.User 和 My.WebServices。

NMCUSTOMDRAW

List-view

NMLVCUSTOMDRAW

Toolbar

NMTBCUSTOMDRAW

Tooltip

NMTTCUSTOMDRAW

Tree-view

NMTVCUSTOMDRAW

  2,指定处理所需的绘制阶段

  正如我在前面提到的,绘制一个控件存在一些“阶段”。特别是,您可以将绘制过程理解为一系列阶段,其中控件通知其父窗口需要绘制的内容。事实上,控件甚至会在绘制控件及其各项前后发送一个通知,从而让编程人员更好地控制该过程。

  在所有情况下,单一的 NM_CUSTOMDRAW 处理程序在每个绘制阶段都进行调用。然而,谨记:自定义绘制允许您在自己的绘制中合并默认的控件绘制,您需要指定您将处理哪个绘制阶段。这通过设置 NM_CUSTOMDRAW 处理程序的第二个参数 (pResult) 完成。事实上,如果您从未设置该值,则用初始阶段的 CDDS_PREPAINT 调用函数后,您的函数将不再被调用!

  从技术上讲,只有两个阶段指定需要的绘制阶段(CDDS_PREPAINT 和 CDDS_ITEMPREPAINT),它们影响发送通知消息的内容。然而,通常只在处理程序的最后指定代码将处理的绘制阶段。表 2 列出用于指定所需绘制阶段(代码关注的)的值。

  表 2:自定义绘制返回标志

自定义绘制返回标志

含义

CDRF_DEFAULT

指示控件自行绘制。该值为默认值,不应该将它与其他值组合在一起。

CDRF_SKIPDEFAULT

用于指定控件根本不进行任何绘制。

CDRF_NEWFONT

当代码更改绘制项/子项的字体时使用。

CDRF_NOTIFYPOSTPAINT

使通知信息在控件或每个项/子项绘制后发送。

CDRF_NOTIFYITEMDRAW

指出项(或子项)将进行绘制。注意,它下面的值与 CDRF_NOTIFYSUBITEMDRAW 相同。

CDRF_NOTIFYSUBITEMDRAW

指出子项(或项)将进行绘制。注意,它下面的值与 CDRF_NOTIFYITEMDRAW 相同。

CDRF_NOTIFYPOSTERASE

当删除控件后需要通知代码时使用。

  以下为一个示例,其中的代码指定,当绘制控件的项 (CDRF_NOTIFYITEMDRAW) 及子项(CDRF_NOTIFYPOSTPAINT),以及绘制完成时,应该调用 NM_CUSTOMDRAW 处理程序。

void CListCtrlWithCustomDraw::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT*pResult)
{
 LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
 ...
 *pResult = 0; // Initialize value
 *pResult |= CDRF_NOTIFYITEMDRAW;
 *pResult |= CDRF_NOTIFYSUBITEMDRAW;
 *pResult |= CDRF_NOTIFYPOSTPAINT;
}

  3,筛选指定的绘制阶段

  一旦指定要关注的阶段后,您需要处理这些阶段。因为绘制过程的每个阶段只有一个消息要发送,惯例是执行一个 switch 语句以决定准确的绘制阶段。不同的绘制阶段由以下标志定义:

CDDS_PREPAINT
CDDS_ITEM
CDDS_ITEMPREPAINT
CDDS_ITEMPOSTPAINT
CDDS_ITEMPREERASE
CDDS_ITEMPOSTERASE
CDDS_SUBITEM
CDDS_POSTPAINT
CDDS_PREERASE
CDDS_POSTERASE

  对于一个 CListCtrl 派生的类,有一个 NM_CUSTOMDRAW 处理程序的示例,其中您可以发现,代码决定当前绘制阶段的方式:

void CMyCustomDrawControl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
 LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
 switch(pNMCD->dwDrawStage)
 {
  case CDDS_PREPAINT:
   ...
   break;
  case CDDS_ITEMPREPAINT:
   ...
   break;
  case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
   ...
   break;
  ...
 }
 *pResult = 0;
}

  注意,为了决定子项(例如,列表视图控件)绘制的阶段,您必需使用按位 or 操作符,它有两个值:其中一个为 CDDS_ITEMPREPAINT 或者CDDS_ITEMPOSTPAINT,另一个为 CDDS_SUBITEM。

  要说明它,我们假定您想在绘制列表视图项之前进行一些处理。将编写 switch 语句来处理 CDDS_ITEMPREPAINT。

case CDDS_ITEMPREPAINT:
...
break;

  然而,如果是您所关注子项的预绘制阶段,则将如下操作:

case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
...
break;

示例:创建一个列表视图控件自定义绘制控件

  如前面提到的,您可以完全控制控件及其项的绘制,或者仅执行一小部分特定于应用程序的绘制,并让控件继续进行。本文的焦点更多地偏重于控件绘制技术而非高级的绘制技术,我们将演练一个简单的示例,其中列表视图控件是一个自定义的绘制,因此项的文本将在创建拼接外观的交替单元中显示为不同的颜色。

  ·创建一个基于 Visual C++ 2005 对话框的项目,名为ListCtrlColor。

  ·从 Class View 中选择 Project 菜单选项,并单击 Add Class 调用 Add Class 对话框。

  ·从分类列表中选择 MFC,然后从模板列表中选择 MFC Class。

  ·单击 Add 按钮,调用 MFC Class Wizard 对话框。

  ·对于 Class name,键入值 CListCtrlWithCustomDraw 并选择 CListCtrl的 Base class。

  ·单击 Finish 按钮,生成类的标头和执行文件。

  ·对于 Class View,右键单击 CListCtrlWithCustomDraw 类,并选择Properties 上下文菜单选项。

  ·显示 Properties 窗口时,单击顶部的 Messages 按钮,显示一个两列的消息列表,您可以为其实现处理程序。

  ·在消息列表中单击 NM_CUSTOMDRAW 项,然后下拉第二列的组合框箭头,并选择值 OnNMCustomdraw。

  ·现在,处理绘制代码。这里,我们只简单处理项和子项预绘制阶段,指定基于当前行(项)和列(子项)的文本和背景色。要进行此操作,按如下所示修改 OnNMCustomdraw 函数:

void CListCtrlWithCustomDraw::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT*pResult)
{
 LPNMLVCUSTOMDRAW lpLVCustomDraw =reinterpret_cast(pNMHDR);
 switch(lpLVCustomDraw->nmcd.dwDrawStage)
 {
  case CDDS_ITEMPREPAINT:
  case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
   if (0 == ((lpLVCustomDraw->nmcd.dwItemSpec+ lpLVCustomDraw->iSubItem) % 2))
   {
    lpLVCustomDraw->clrText =RGB(255,255,255); // white text
    lpLVCustomDraw->clrTextBk =RGB(0,0,0); // black background
   }
   else
   {
    lpLVCustomDraw->clrText =CLR_DEFAULT;
    lpLVCustomDraw->clrTextBk =CLR_DEFAULT;
   }
   break;
   default: break;
 }
 *pResult = 0;
 *pResult |= CDRF_NOTIFYPOSTPAINT;
 *pResult |= CDRF_NOTIFYITEMDRAW;
 *pResult |= CDRF_NOTIFYSUBITEMDRAW;
}

  现在,我们来测试新控件。要进行此操作,您只需使用 CListCtrlWithCustomDraw 类将列表视图控件放在对话框中,并对其进行子类派生。下面是完成该操作的步骤。

  ·在 Resource 视图中,打开应用程序的主对话框 (IDD_LISTCTRLCOLOR_DIALOG)。

  ·从 Toolbox 中,将一个 List Control 拖放到该对话框。

  ·右键单击列表控件,并选择 Properties 上下文菜单选项。

  ·将 View 属性设置为 Report。

  ·右键单击控件,并选择 Add Variable 上下文菜单选项。

  ·出现 Add Member Variable Wizard 对话框时,指定m_lstBooks 的 Variable name,并单击 Finish 按钮。

  ·这时,您就有了一个 CListCtrl 派生类 (m_lstBooks),它将对话框上的列表视图控件进行子类派生。然而,m_lstBooks 需要从最新创建的 CListCtrlWithCustomDraw 派生,以便于调用您的绘制代码。因此,打开对话框的标题文件 (ListCtrlColorDlg.h),将 m_lstBooks 更改为 CListCtrlWithCustomDraw 类型。

  ·在 CListCtrlColorDlg 类开始之前,添加以下指令。

#include"ListCtrlWithCustomDraw.h"

  ·将下面的代码添加到对话框的 OnInitDialog 成员函数,这样我们就能够看到一些列表视图行。

// Insert the columns
m_lstBooks.InsertColumn(0, _T("Author"));
m_lstBooks.InsertColumn(1, _T("Book"));
// Define the data
static struct
{
 TCHAR m_szAuthor[50];
 TCHAR m_szTitle[100];
} BOOK_INFO[] = {
 _T("Tom Archer"),_T("Visual C++.NET Bible"),
 _T("Tom Archer"),_T("Extending MFC with the .NET Framework"),
 _T("Brian Johnson"),_T("XBox 360 For Dummies")
};
// Insert the data
int idx;
for (int i = 0; i < sizeof BOOK_INFO / sizeof BOOK_INFO[0]; i++)
{
 idx = m_lstBooks.InsertItem(i,BOOK_INFO[i].m_szAuthor);
 m_lstBooks.SetItemText(i, 1,BOOK_INFO[i].m_szTitle);
}

  ·现在,建立并运行应用程序。图 1 为应用程序外观的一个示例。


  图 1. 自定义绘制示例应用程序

  小结

  当 Windows 首次作为“下一代”操作系统引入到应用程序开发之中时,它作为新图形用户界面的一个主要论据就是其一致性。该论据的要点所在是其具有一个通用的外观:统一的菜单项、通用控件等。这一通用性的感觉可能会一直延续,直到有第二家公司想设计其自己的应用程序。简单说,提供外观与其他应用程序雷同的应用程序,任何公司都不会逃离这一怪圈。

  要建立一个唯一的且让人过目难忘的用户界面,其中一种方式是为应用程序设计并开发自定义的控件。希望本文能对您有所帮助,现在,您了解到一种非常强大的技术,它使您的应用程序能从众多竞争对手的应用程序中脱颖而出。

OWNER DRAW实现自绘按钮

一、准备工作

 在开始编码之前,首先应该确定好,更准确的说应该是设计好按钮在各种状态下的外观。按钮控件的几中基本状态包括:

Normal状态,就是按钮一开始显示时的样子。

Over状态,鼠标指针移动到按钮上面时按钮显示的样子。

Down状态,按下按钮时显示的样子。

Focus状态,按钮按下后松开的样子,例如标准按钮按下松开之后会看到按钮内部有一个虚线框。

Disable状态,当然就是按钮被设置成无效的时候的样子啦。

 

我参考了一下WindowsXP中普通按钮的实际样子,设计出XP按钮各种状态的外观,如下图所示:

 

至于Down状态主要是在Over状态的基础上将文字往右下的方向稍微平移,以实现下压的效果。

 

 二、实现原理及难点

 

下面我们开始类的创建,在Workspace的ClassView页中右击列表树的根结点,选择New Class…

 

  

在弹出窗口中进行派生类的定义,如下图所示,注意,你需要填写的只有Name和Base class两项,其余的选项保持默认值就可以了。

  

  下面简要叙述一下按钮的实现原理

 

1. 在控件初始化时为按钮添加Owner  Draw的属性。这是因为在MFC中,要想激活控件的自绘功能,要求该控件的属性中必须包含属性值BS_OWNERDRAW,这一步我们可以通过类向导为 CXPButton类添加PreSubclassWindow()函数,在该函数中完成属性值的设置。当激活控件的自绘功能之后,每次控件状态改变的时候都会运行函数DrawItem(),该函数的作用就是绘制控件在各种状态下的外观。

 

2. 添加WM_MOUSELEAVE消息函数,当鼠标指针离开按钮时,触发该消息函数,我们在函数中添加代码,通知DrawItem函数鼠标指针已经离开了,让按钮重绘。

 

3. 添加WM_MOUSEHOVER消息函数,当鼠标指针位于按钮之上时,触发该消息函数,我们在函数重添加代码,通知DrawItem函数鼠标指针现在正在按钮的上面,让按钮重绘。

 

4. 添加DrawItem函数。在DrawItem中根据按钮当前的状态绘制按钮的外观。可以说自绘控件的大部分功能都是在这个函数中实现的。DrawItem函数包含了一个LPDRAWITEMSTRUCT的指针,本篇会在稍后予以讲解。

 

这里有两个难点,首先是WM_MOUSELEAVE和 WM_MOUSEHOVER不是标准的Windows消息函数,它们不能通过类向导来添加,所有的添加工作都需要通过手工输入代码来完成。另一个难点是 DrawItem中的LPDRAWITEMSTRUCT指针,它指向了一个DRAWITEMSTRUCT的结构,这个结构中包含了控件的各种细节,为我们提供了实现自绘功能的必要信息。

难点一:

事实上WM_MOUSELEAVE和WM_MOUSEHOVER两个Windows消息是通过WM_MOUSEMOVE消息触发的,而 WM_MOUSEMOVE是标准的Windows消息,因此我们可以通过类向导来为CXPButton类添加WM_MOUSEMOVE消息函数。

  

函数的代码见如下,这段代码非常有用,在其它的自绘控件中,如果想触发WM_MOUSELEAVE和WM_MOUSEHOVER消息,也是使用类似的方法实现的。

 

voidCXPButton::OnMouseMove(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler codehere and/or call default

       if (!m_bTracking)

       {

              TRACKMOUSEEVENT tme;

              tme.cbSize = sizeof(tme);

              tme.hwndTrack = m_hWnd;

              tme.dwFlags = TME_LEAVE |TME_HOVER;

              tme.dwHoverTime = 1;

             m_bTracking = _TrackMouseEvent(&tme);

       }

       CButton::OnMouseMove(nFlags, point);

}

我们接着添加WM_MOUSELEAVE和WM_MOUSEHOVER消息消息函数。在CXPButton类的声明中(即在 XPButton.h文件中)找到afx_msg void OnMouseMove(UINT nFlags, CPoint point);的函数声明,紧接其下输入

 

afx_msgLRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);

afx_msgLRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);

 

然后在XPButton.cpp文件中找到ON_WM_MOUSEMOVE(),紧接其后输入

ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)

ON_MESSAGE(WM_MOUSEHOVER,OnMouseHover)

 

难点二:

下面我们看看DRAWITEMSTRUCE结构为我们提供了哪些有用信息呢?

DRAWITEMSTRUCT结构的定义如下:

 

typedefstruct  tagDRAWITEMSTRUCT

{

    UINT  CtlType;                      //控件类型

    UINT  CtlID;                        //控件ID

    UINT  itemID;                       //菜单项、列表框或组合框中某一项的索引值

    UINT  itemAction;                    //控件行为

    UINT  itemState;                     //控件状态

    HWND  hwndItem;                   //父窗口句柄或菜单句柄

    HDC   hDC;                        //控件对应的绘图设备句柄

    RECT  rcItem;                       //控件所占据的矩形区域

    DWORD itemData;                   //列表框或组合框中某一项的值

}DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT;

其实不仅是按钮控件,其它控件,如ComboBox、ListBox、StaticText等都是通过DRAWITEMSTRUCT来记录控件信息的。关于这个结构的详细文档可参考本篇的附录。

 

也许你早已看到许多自绘按钮的例子,实际上自绘按钮本身的函数结构都是差不多的,它们显示效果的区别主要取决于代码编写者对GDI作图函数的运用与掌握程度。有兴趣的朋友可以研究一下CXPButton类中DrawItem函数的数据结构,其实只要修改一下其中GDI绘图函数的部分代码,马上又能做出另一个自绘按钮控件了。

 

 三、按钮类的使用

 

下面演示CXPButton类的使用。往对话框中添加一个按钮控件,假设它的ID值为IDC_BUTTON1。进入类向导(Class Wizard)的Member Variables属性页,为IDC_BUTTON1添加一个变量m_btnNormal。确定退出后再进行编译,就可以看到重新定义过XP风格按钮了。

 

 如果你是之间把CXPButton的源文件引入自己的工程中的,那么在上图的Variable type中是看不到CXPButton选项的。但是可以通过以下方法加入:

 

1. 首先保存工程后退出。

2. 在工程的目录下找到一个后缀名为.clw的文件,将其删除。但是为了以防万一还是建议你实现备份一下。

3. 重新打开工程,进入类向导,此时会看到一下一个弹出对话框,我们选择“是(Yes)”。

   

4. 再选择“Add All”,这样我们就可以在类向导中使用CXPButton的变量类型了。

 

 四、小结与提示

 

对于按钮来说,当按钮上面任何可见的部分发生变换的时候,都要调用DrawItem函数进行重绘。自绘制按钮必须设定BS_OWNERDRAW的属性,设置的代码在PreSubclassWindows函数中完成。另外为了防止系统字体设置的变化影响控件的表达效果,还可以在该函数中为控件指定某种固定的字体。但是要注意的是这个

让我们来回顾一下实现自绘按钮的基本步骤:

a. 确定设计方案;

b. 初始化,但是记得要在函数退出前恢复先前的GDI对象,并释放所占领的资源;

c. 添加相应消息函数;

d. 添加绘图函数DrawItem,在DrawItem中作图的顺序一般是先画外边框,再上底色,接着写文字,最后是画内边框。不过有些人也喜欢把边框放到最后画,这问题不大。

 

 五、附录

 

DRAWITEMSTRUCT结构文档 (根据Msdn翻译)

 

DRAWITEMSTRUCT

 

DRAWITEMSTRUCT为需要自绘的控件或者菜单项提供了必要的信息。在需要绘制的控件或者菜单项对应的WM_DRAWITEM消息函数中得到一个指向该结构的指针。 DRAWITEMSTRUCT结构的定义如下:

 

typedef  struct  tagDRAWITEMSTRUCT

{

UINTCtlType ;

UINTCtlID ;

UINTitemID ;

UINTitemAction ;

UINTitemState ;

HWNDhwndItem ;

HDC hDC;

RECTrcItem ;

ULONG_PTRitemData ;

} DRAWITEMSTRUCT;

 

结构成员:

 

CtlType

指定了控件的类型,其取值如下表所示。

取值

描述

ODT_STATIC

静态文本控件

ODT_BUTTON

按钮控件

ODT_COMBOBOX

组合框控件

ODT_LISTBOX

列表框控件

ODT_LISTVIEW

列表视图控件

ODT_MENU

菜单项

ODT_TAB

Tab控件

 

CtlID

指定了自绘控件的ID值,而对于菜单项则不需要使用该成员

 

itemID

表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为–1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。

 

itemAction

指定绘制行为,其取值可以为下表中所示值的一个或者多个的联合。

 

取值

描述

ODA_DRAWENTIRE

当整个控件都需要被绘制时,设置该值

ODA_FOCUS

如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。

ODA_SELECT

如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。

 

itemState

指定了当前绘制操作完成后,所绘项的可见状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值可以为下表中所示值的一个或者多个的联合。

 

取值

描述

ODS_CHECKED

如果菜单项将被选中,则可设置该值。该值只对菜单项有用。

ODS_COMBOBOXEDIT

在自绘组合框控件中只绘制选择区域。

ODS_DEFAULT

默认值。

ODS_DISABLED

如果控件将被禁止,则设置该值。

ODS_FOCUS

如果控件需要输入焦点,则设置该值。

ODS_GRAYED

如果控件需要被灰色显示,则设置该值。该值只在绘制菜单时使用。

ODS_HOTLIGHT

Windows 98/Me, Windows 2000/XP: 如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。

ODS_INACTIVE

Windows 98/Me, Windows 2000/XP: 表示没有激活的菜单项。

ODS_NOACCEL

Windows 2000/XP: 控件是否有快速键盘。

ODS_NOFOCUSRECT

Windows 2000/XP: 不绘制捕获焦点的效果。

ODS_SELECTED

选中的菜单项。

 

hwndItem

指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象时菜单项,则表示包含该菜单项的菜单句柄。

 

hDC

指定了绘制操作所使用的设备环境。

 

rcItem

指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说 rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。

 

itemData

对于菜单项,该成员的取值可以是由

CMenu::AppendMenu、

CMenu::InsertMenu或者

CMenu::ModifyMenu

等函数传递给菜单的值。

 

对于列表框或这组合框,该成员的值可以为由

ComboBox::AddString、

CComboBox::InsertString、

CListBox::AddString或者

CListBox::InsertString

等传递给控件的值。

 

如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC, itemData的取值为0。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值