分享
 
 
 

Visual Studio 2005:在 Visual C++ 中开发自定义的绘图控件

王朝vc·作者佚名  2006-04-22
窄屏简体版  字體: |||超大  

Visual Studio 2005:在 Visual C++ 中开发自定义的绘图控件

发布日期: 2006-4-14 | 更新日期: 2006-4-14

Tom Archer

Program Manager, Microsoft

适用于:

Win32 API

Microsoft 基础类

Visual C++ 2005

摘要:TomArcher介绍自定义的绘图技术 — 开发自定义控件,使自己的应用程序具有独特的外观。

下载相关的示例代码,CustomDraw.exe

本页内容

您想变得有多与众不同?

主宰绘图操作

实现自定义绘制的三步曲

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

小结

致谢

参考资料

至今我仍然记得一次对话(回首 1995 年,那时我在 Peachtree Software 管理一支开发团队),话题是关于 Visual C++ 和 MFC 能为我们节约多少时间,从而使计帐系统的上市时间能加快多少。大概情况是这样的:

我:Visual Studio 向导将使我们能够在几秒钟的时间内生成应用程序的框架。我们基本上可以免费得到所有用户界面。从菜单、状态栏、完整的文档/视图结构,到单独的数据和演示文稿、工具栏等。甚至,它们还在其中生成了类似于文件打开、打印和打印预览的功能!

市场人员:听起来不错。那么,你们完成全部编码要用多长时间?

我:鉴于我们得到的所有 UI 都是免费的,并且只需加入有关计帐的内容,所以会花 6 到 9 个月的时间完成编码。最棒的是,该应用程序的外观会象一个 Microsoft Office 应用程序一样!

市场人员:哦?

我:真的。从具有 Microsoft 风格的应用程序中,我们可以获得潜在的好处。这一点特别重要,如果我们的应用程序象个 Office 产品,那么在其包装盒上打上 Windows 95 徽标会更容易。

市场人员:我们不能在市场上大喊“购买我们的产品吧,它多象 XXX 产品呀。”所有的计帐产品都具有相同的基本功能。我们的产品区别于其他产品的唯一方式就是用户界面。我们要雇佣图形设计师来设计一个完全自定义的用户界面,然后您的团队再进行编码。这要花多少时间?

我:在我们没看到准确的控件前,很难说会花多长时间,但至少这样会加倍我们的工作。

市场人员:那么,你们最好尽快开始。

两年后,Peachtree Software 发布了自己第一个从零开始设计并创建的产品,并且作为其中的一份子,我也以此为而骄傲。经过 10 年的变迁,我也带头进行了一些知名产品(分别为 IBM、AT&T 和 VeriSign)的开发,这些产品在全球数百万的 PC 和电话上运转着,期间,我始终记得一个教训:不管应用程序在内部运行时有多好,但如果它不能在纷繁的产品中脱颖而出并抓住用户的心,那么它也卖不出去。

因此,当我在 MSDN 发表第一篇文章时,我考虑最好着眼于一个我感兴趣的题目 — 过去我在 Peachtree 经常使用的一项技术,用来开发一些市场部门需要的奇妙的 UI 部件 — 开发自定义的绘图控件。

您想变得有多与众不同?

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

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

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

返回页首

主宰绘图操作

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

处理 WM_PAINT

所有者绘制

自定义绘制

处理 WM_CTLCOLOR

处理 WM_PAINT

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

所有者绘制

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

自定义绘制

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

处理 WM_CTLCOLOR

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

返回页首

实现自定义绘制的三步曲

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

执行一个 NM_CUSTOMDRAW 消息处理程序。

指定处理所需的绘制阶段。

筛选特定的绘制阶段(在这些阶段中,您需要加入自己的特定于控件的绘制代码)。

执行一个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

指定处理所需的绘制阶段

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

在所有情况下,单一的 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;

}

筛选指定的绘制阶段

一旦指定要关注的阶段后,您需要处理这些阶段。因为绘制过程的每个阶段只有一个消息要发送,惯例是执行一个 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 首次作为“下一代”操作系统引入到应用程序开发之中时,它作为新图形用户界面的一个主要论据就是其一致性。该论据的要点所在是其具有一个通用的外观:统一的菜单项、通用控件等。这一通用性的感觉可能会一直延续,直到有第二家公司想设计其自己的应用程序。简单说,提供外观与其他应用程序雷同的应用程序,任何公司都不会逃离这一怪圈。

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

返回页首

致谢

我要感谢 Microsoft 的项目经理 Andrew Whitechapel,我们合著有两本书籍(Inside C#, Second Edition 和 Visual C++.NET Bible)。几年来,我从 Andrew 那里学到很多东西,包括本文中我写到的一些内容。

返回页首

参考资料

Custom Draw Overview

Custom Draw Reference

关于作者

Tom Archer 是 Microsoft 的一位项目经理。他曾经是 C++ MVP,Tom 现负责 Visual C++、Windows Vista 以及 MSDN 中的 Windows SDK Developer Centers。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有