jues

自绘控件的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。

 

 

摘于: http://blog.csdn.net/weiwangchao_/article/details/6832566

MFC NM_CUSTOMDRAW

继承类的方式

BEGIN_MESSAGE_MAP(CTreeCtrlEx, CTreeCtrl)
//{{AFX_MSG_MAP(CTreeCtrlEx)
    ON_NOTIFY_REFLECT( NM_CUSTOMDRAW, On_NmCustomDraw ) 
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CTreeCtrlEx::On_NmCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	
	LPNMTVCUSTOMDRAW lpnmcd = (LPNMTVCUSTOMDRAW) pNMCD;
	if (lpnmcd ->nmcd.dwDrawStage == CDDS_PREPAINT)
	{
		
		*pResult = CDRF_NOTIFYITEMDRAW;
		return;
	}
	else if (lpnmcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT ) 
	{
#if 1
		CRect rcItem(pNMCD->rc); // Item的区域
		POINT ptItem ;
		ptItem.x = rcItem.left  ;
		ptItem.y = rcItem.top  ;
		UINT uFlags; 
		HTREEITEM hItem = HitTest(ptItem, &uFlags); // Item 句柄
		CImageList  *img_list = this->GetImageList( TVSIL_NORMAL );
		int i,i_s;
		if(!hItem)
		{
			*pResult = CDRF_DODEFAULT;
			return;
		} 
		
		HDC hdcItem = pNMCD->hdc ; // Item的dc
		CDC *dc = CDC::FromHandle( hdcItem );
		CString strText;
		strText=GetItemText(hItem); 

		this->GetItemImage( hItem,i,i_s );
		dc->DrawIcon( rcItem.left,rcItem.top,img_list->ExtractIcon( i )  );
		rcItem.left += 32;
		dc->DrawText(strText,rcItem,DT_TOP );
		rcItem.top += 16;
		dc->DrawText("副标题",rcItem,DT_TOP );
		*pResult = CDRF_SKIPDEFAULT; 
		return; 
#else
		CRect rcItem(pNMCD->rc); // Item的区域
		POINT ptItem ;
		ptItem.x = rcItem.left + 1 ;
		ptItem.y = rcItem.top + 1 ;
		UINT uFlags; 
		HTREEITEM hItem = HitTest(ptItem, &uFlags); // Item 句柄
		if(!hItem)
		{
			*pResult = CDRF_DODEFAULT;
			return;
		} 
		
		HDC hdcItem = pNMCD->hdc ; // Item的dc
		CDC dc ;//= CDC::FromHandle( hdcItem );
		CString strText;
		strText=GetItemText(hItem); 
		dc.Attach(hdcItem); 
		 int nSave = dc.SaveDC(); 
		//dc.SetBkMode(TRANSPARENT);
		dc.DrawText( "111",rcItem,DT_TOP );
		 dc.RestoreDC(nSave);
		dc.Detach(); 
		*pResult = CDRF_SKIPDEFAULT; 
		return; 
#endif
	}
	*pResult = CDRF_DODEFAULT;
	return; 
	
}

 

	afx_msg void On_NmCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);

一步一步教你实现CTreeCtrl 自绘

一步一步教你实现CTreeCtrl 自绘

  -------BY wojiushi3344   

QQ:513670524  转载请说明出处

 

             源代码下载

    最近因工作需求,需要自绘CTreeCtrl。由于原来从来没有自绘过,开始在网上搜索资料,查询(因此本文有些知识可能不全面,或许还有更好的办法来实现,还请大家多多指教。)经过一段时间的编写,终于写好了。在此,感谢网友bunpkin提供的实例参考。

先贴上效果图,如果觉得还不错,那就继续往下看吧。如果觉得不行的,请飘过。

 如何你看见这句话我会很高兴,因为至少我写的东西对你还是有一点点的吸引了。在此谢过!

很好,那现在让我们来说说为什么要自绘CTreeCtrl。我总结了以下2点需要自绘的情况。

1.当系统自带的树形控件已不满足我们的要求时,我们需要自绘。就像上图一样我们需要在后面显示我们额外的图标。

2.当你是一个追求界面美观的人时,我们需要自绘

 我们需要自绘CTreeCtrl控件,我们就必须先了解一下自绘的方法,

CTreeCtrl自绘有2种方法可以实现。

第一种:通过从写NM_CUSTORMDRAW反射消息实现自绘。

第二种 通过重写   ON_PAINT实现自绘。

 二种方法都是通过继承CTreeCtrl类,然后重写虚函数实现。

  下面分别介绍每一种的方法:

 第一种:通过从写NM_CUSTORMDRAW反射消息实现自绘。从这个消息的英文单词我们翻译过来就是自定义绘制。当CTreeCtrl控件需 要绘制就会触发这个消息。需要注意的是这个函数被调用的时候只是绘制了当前的某一个节点,意思就是当我们的CTreeCtrl有10个节点需要绘制的时候 这个函数就需要调用10次。

这个是函数原型

void CMyTreeCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)

这个函数会给我们传入一个pNMHDR指针,这个指针有我们很关心的数据,如当前的HDC,RECT,和当前的节点信息,但是必须要通过转换。下面是转换语句。

NMTVCUSTOMDRAW *ptvTreeCtrl=(NMTVCUSTOMDRAW *)pNMHDR

 可能有的朋友会问为什么需要类型转换了,这是由于在我们的程序中收到NM_CUSTORMDRAW消息的不止CTreeCtrl一个,其它的控件也能收到,这里我们我为了区分是哪个控件收到的消息所以我们需要对应的类型转换。下面是常见控件的类型转换类型。

 

Control Structure
List view NMLVCUSTOMDRAW
ToolTips NMTTCUSTOMDRAW
Tree view NMTVCUSTOMDRAW
All other supported controls NMCUSTOMDRAW

 很明显我们根据上面的图一眼就能看出CTreeCtrl对应的类型是NMTVCUSTOMDRAW。

下面我们在来看看我们最关心的NMTVCUSTOMDRAW 结构里面存的是什么数据。

NMTVCUSTOMDRAW结构定义:
typedef struct tagNMTVCUSTOMDRAW {
 NMCUSTOMDRAW nmcd;//包含控件的基本信息(见下表)

 COLORREF clrText;//节点的文本颜色

 COLORREF clrTextBk;//文本背景色
}NMTVCUSTOMDRAW, *LPNMTVCUSTOMDRAW;

 NMCUSTOMDRAW结构定义:
typedef struct tagNMCUSTOMDRAWINFO {
 NMHDR hdr;//跟pNMHDR一样,我基本没用到

 DWORD dwDrawStage;//绘画段,某项被檫出前,后,绘制前,后

  HDC hdc;//控件的设备上下文句柄
 RECT rc;//要绘制的区域
 DWORD dwItemSpec;//树控件不需要这个变量
 UINT uItemState;//项的状态,只要是点击选中 

LPARAM lItemlParam //项关联的数据,通过SetItemData函数设置的。
}NMCUSTOMDRAW, FAR* LPNMCUSTOMDRAW;

  uItemState项的状态(来自MSDN)

Specifies the current item state. It can be a combination of the following values.
Value              Description
CDIS_CHECKED       The item is checked.  项被核记了

CDIS_DEFAULT       The item is in its default state. 默认状态

CDIS_DISABLED      The item is disabled.  项被禁止了

CDIS_FOCUS         The item is in focus.  项具有焦点
CDIS_GRAYED        The item is grayed.   项为灰颜色,
CDIS_HOT           The item is currently under the pointer (hot).  鼠标当前停留在这个项上

CDIS_SELECTED      The item is selected.  项被选中了

 以上就是我们自绘需要知道的数据结构,如果你了解这些数据结构所代表的意思,那下面我们就可以开始绘制了。

 

绘制方法

NMCUSTOMDRAW消息自绘,使你可以决定在什么绘画端(就是NMCUSTOMDRAW 中的DWORD dwDrawStage)来绘制,比较常用的是在绘制前的阶段来绘制,如果你只是用了这种方法来绘制画,那么恭喜你选择对了一半,但是绘制失败了,因为你将什么也看不见。不急,让我们慢慢给你说明原因,因为你在绘制前的阶段绘制了,紧接这系统还会调用一次默认绘制,那么你原来的绘制就被覆盖了。

 正确的方法是在绘制前绘制,然后过滤点系统的默认绘制,使之不在调用。这样我们所绘制就能看见了。于是乎在我们的OnNMCustomdraw函数中多了一下几句代码。(过滤系统的默认绘制)

  1. if (lpnmcd ->nmcd.dwDrawStage == CDDS_PREPAINT)    
  2.  {    
  3.     *pResult = CDRF_NOTIFYITEMDRAW;    
  4.      return;    
  5.   }    
  6. else if (lpnmcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)    
  7.   {    
  8. //自定义绘制  
  9. *pResult = CDRF_DODEFAULT;    
  10.    return;    
  11.  }  

很好,现在你已经知道了绘制的基本方法了,那么接下来你就可以加上你自己的绘制了。

大致思路如下。

获取当前绘制节点的信息。如:节点状态,节点区域,节点文字等信息

需要掌握的函数:

CTreeCtrl::GetItemRect

BOOL GetItemRect( HTREEITEM hItem, LPRECT lpRect, BOOL bTextOnly );

返回值:
如果项是可视的则返回非零值,以及包含在lpRect中的边界矩形。否则,返回0和没有被初始化的lpRect。

参数:

hItem 一个tree view项的句柄。
lpRect 指向一个用来接收边界矩形的RECT结构的指针。其中的坐标是相对于该tree view控件的左上角的。
bTextOnly 如果这个参数是非零值,则边界矩形值包括项的文本。否则,它包括该项在tree view控件所占据的整个一行。

CTreeCtrl::GetItemText

CString GetItemText( HTREEITEM hItem ) const;

返回值:返回一个包含该项的文本的CString对象。

参数:

hItem 要获取其文本的项的句柄。

添加自己的绘制函数,如绘制图片,绘制文字等。

 CTreeCtrl::ItemHasChildren

BOOL ItemHasChildren( HTREEITEM hItem );

返回值:
如果由hItem指定的tree项有子项则返回非零值;否则返回0。

参数:

 

hItem 一个tree项的句柄。

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


如过你讨厌记住以上的这么多数据,那么恭喜你,你可以继续网下看,下面介绍另一种实现自绘的方法.通过重写 ON_PAINT消息来实现自绘,也是我最终实现自绘的方法。因为我发现通过上面的方法来实现自绘解决不了我的问题。闪烁和热点,也许是我自己的能力有 限。大家可以分享一下你们是如何解决我遇到的问题的。

 原理:

我们获取树形控件的数据结构和DC,然后我们自己来定义绘制的规则。这里就可以发挥你的DIY兴趣了,想怎么画就怎么画。

 很好,下面让我们进入另一种方法的介绍

首先我们要明白ON_PAINT消息在什么情况下触发,在win32程序中,当窗口需要重绘的时候会触发ON_PAINT消息,还有一种情况就是我 们自己手动触发,手动触发的消息有两种,第一种是调用窗口无效函数Invalidate(FALSE),第2种是手动发送wm_paint消息。

明白了这个我们才知道我们以后需要窗口重绘的时候怎么处理,这里说点题外话,原来在做扫雷游戏的时候,鼠标单击之后要过20多毫秒才有反应,后来查了半天原因才发现是带用重绘的问题,因为我开始单机之后没有立即调用重绘,而是没隔50毫秒调用一次。

我们通过重写ON_PAINT消息就能获取当前窗口的DC,在这里我们就是整个树形控件的DC,不向上面那种方法一样只获取到树形控件某节点的DC,获取整个DC要比上面那种方法操作方便一些。

下面是onpaint方法:

void CMyCtreeCtrl::OnPaint()

CPaintDC dc(this); //这句就是获取绘制的DC

现在有了DC我们就可以绘制了,在这里我们为了让它绘制的时候不闪烁我们用双缓冲,于是乎我们有了下面的代码。

  1. CPaintDC dc(this); // device context for painting  
  2.  GetClientRect(&m_ClientRect);  
  3.  CBitmap bitmap;  
  4.  CDC MemeDc;  
  5.  MemeDc.CreateCompatibleDC(&dc);  
  6.  bitmap.CreateCompatibleBitmap(&dc, m_ClientRect.Width(), m_ClientRect.Height());  
  7.  CBitmap *pOldBitmap = MemeDc.SelectObject(&bitmap);  
  8.  DrawBack(&MemeDc);  
  9.  DrawItem(&MemeDc);  
  1. dc.BitBlt( m_ClientRect.left, m_ClientRect.top, m_ClientRect.Width(), m_ClientRect.Height(), &MemeDc, 0, 0,SRCCOPY);  
  2. MemeDc.SelectObject(pOldBitmap);  
  3. MemeDc.DeleteDC();  
  1. 可以看见我们在函数中首先绘制了背景然后再背景上绘制了控件。具体的函数实现请看下面的介绍。  

在这里我们基本的东西已经具备了现在我们就差数据了,首先我们要实现我们最开始的那种效果,我们需要定义一个结构体来存储这些数据。

struct TREE_STRUCT
{
 int s_FirstImage;                  //第一张图片的信息
 int s_SecondImage;            //第二张图片的信息
 int s_ThreeImage;                //第三张图片的信息
 int s_FourImage;                  //第四张图片的信息
 COLORREF s_TextColor;   //文字的颜色
 int    s_PeopleNum;              //人的数目
 CString  s_ItemStr;                //每一项的文字
 CString  s_StrUrl;                   //每一项对应的URL地址

我们在定义一个map

map <HTREEITEM,TREE_STRUCT>  m_mapTree; 这是为了我们以后的绘制和判断热点用。

 

有了数据结构,我们现在就可以插入数据使之成为一颗拥有节点的数,这个插入我们也需要自己重写,因为插入的数据是我们自己定义的。

 

  1. HTREEITEM CMyCtreeCtrl::InsertItemEx(TREE_STRUCT pStruct,HTREEITEM lparent,HTREEITEM  lpFont )//插入项  
  2. {  
  3.     HTREEITEM tempTreeItem;  
  4.     CString str;  
  5.     str.Format("%s(%d人)",pStruct.s_ItemStr,pStruct.s_PeopleNum);  
  6.     tempTreeItem = InsertItem(str,lparent,lpFont);  
  7.     m_mapTree.insert(pair<HTREEITEM,TREE_STRUCT>(tempTreeItem,pStruct));  
  8.     return  tempTreeItem;  
  9.   
  10. }  

 

下面是绘制树形控件的具体实现 

  1. void CMyCtreeCtrl::DrawItem(CDC* pDc)  
  2. {  
  3.  HTREEITEM currentItem,parentItem;//当前的句柄,和它的父节点的句柄  
  4.  DWORD    treeStyle;// 数的类型  
  5.  CRect    itemRect;//每一项的区域  
  6.  int      itemState;//某项的状态  
  7.  //bool selected;    //True:表示是需要高亮   
  8.   
  9.  ImageAttributes alphaAttribut;   
  10.  alphaAttribut.SetColorKey(Color::Fuchsia,Color::Fuchsia);   
  11.   
  12.  treeStyle =:: GetWindowLong( m_hWnd, GWL_STYLE );  
  1. currentItem = GetFirstVisibleItem();//获取第一个课可见的项  
  2. do   
  3. {  
  4.  if (GetItemRect(currentItem,itemRect,TRUE))  
  5.  {  
  6.   itemRect.left=itemRect.left-19;  
  7.   CRect   fillRect(0,itemRect.top,m_ClientRect.right,itemRect.bottom);  
  8.   itemState = GetItemState(currentItem,TVIF_STATE);  
  9.   if (itemRect.top>m_ClientRect.bottom)  //说明这一项已超出窗口的边界,所以不绘制,很好理解吧!不用我说了撒  
  10.   {  
  11.    break;  
  12.   }  
  13.   //绘制鼠标热点  
  14.   if (currentItem==m_MouseMoveItem&&ItemHasChildren(currentItem)==NULL)  
  15.   {  
  16.    m_Gdiplus.usFillRectangle(pDc->m_hDC,fillRect,0xB7F0FE,0xB7F0FE,edoVertical,true);  
  17.   }  
  18.   
  19.   if(itemState&TVIS_SELECTED)  
  20.   {  
  21.    m_Gdiplus.usFillRectangle(pDc->m_hDC,fillRect,0xFF00BB,0xFF00BB,edoVertical,true);  
  22.   
  23.   }  
  24.   //绘制展开图片  
  25.   if (ItemHasChildren(currentItem))  
  26.   {  
  27.    CPoint point;  
  28.    point.x = itemRect.left;  
  29.    point.y = itemRect.top+(itemRect.Height()-m_OpenHigh)/2;  
  30.   
  31.    if (itemState & TVIS_EXPANDED)  
  32.    {  
  33.     m_IconList.Draw(pDc,1,point,ILD_TRANSPARENT);  
  34.    }else  
  35.    {  
  36.     m_IconList.Draw(pDc,0,point,ILD_TRANSPARENT);  
  37.    }  
  38.   
  39.   }  
  40.   itemRect.left+=m_OpenWidth+2;  
  41.   itemRect.right+=m_OpenWidth+8;  
  42.   //绘制图标1  
  43.    m_iter=m_mapTree.find(currentItem);  
  44.    Graphics tempGraphics(pDc->m_hDC);  
  45.    Rect rcDes;  
  46.   
  47.   if (m_iter->second.s_FirstImage>=0&&m_iter->second.s_FirstImage<m_IconNum)  
  48.   {  
  49.    rcDes=Rect(itemRect.left,fillRect.top+m_IconSpacing,m_IconWidt,m_IconHigh);  
  50.   tempGraphics.DrawImag(m_IconBitmap,rcDes,m_iter>second.s_FirstImage*m_IconWidt,0,m_IconWidt,m_IconHigh,UnitPixel,&alphaAttribut);  
  51.   itemRect.left =itemRect.left+m_IconWidt;  
  52.   itemRect.right = itemRect.right+m_IconWidt;  
  53.   
  54.   }  
  1.    //绘制文字  
  2.    DrawItemText(pDc,currentItem,itemRect);  
  3.   
  4.    //绘制后面的第2,3,4项图标  
  5.    CSize  fontSize;  
  6.    fontSize= pDc->GetTextExtent(GetItemText(currentItem));  
  7.    itemRect.left+=fontSize.cx;  
  8.    if (m_iter->second.s_SecondImage>=0&&m_iter->second.s_SecondImage<m_IconNum)  
  9.    {  
  10.     rcDes=Rect(itemRect.left,fillRect.top+m_IconSpacing,m_IconWidt,m_IconHigh);  
  11.     tempGraphics.DrawImage(m_IconBitmap,rcDes,m_iter->second.s_SecondImage*m_IconWidt,0,m_IconWidt,m_IconHigh,UnitPixel,&alphaAttribut);  
  12.     itemRect.left=itemRect.left+m_IconWidt+4;  
  13.    }  
  14.   
  15.   }  
  16.  } while ((currentItem=GetNextVisibleItem(currentItem)) != NULL);  
  17. }  


 

函数的主要思路首先通过GetFirstVisibleItem();获取第一个可见的节点,如果节点存在则按照我们的绘制顺序绘制,然后调用GetNextVisibleItem获得下一个可见的节点,一直循环,直到下一个可见节点为NULL的时候退出绘制函数。

下面讲一下获取项热点的方法。同样我们需要重写消息,这次是ON_MOUSEMOVE消息。

我们首先定义一个数据来记录当前鼠标移动到的节点的句柄。具体看代码。

 

  1. HTREEITEM        m_MouseMoveItem;                                          //鼠标移动到的项  
  2. void CMyCtreeCtrl::OnMouseMove(UINT nFlags, CPoint point)  
  3. {  
  4.  // TODO: 在此添加消息处理程序代码和/或调用默认值  
  5.   m_ptOldMouse = point;  
  6.  HTREEITEM hItem = HitTest(point);  
  7.  if ( hItem != NULL && hItem != m_MouseMoveItem )  
  8.  {  
  9.   m_MouseMoveItem = hItem;  
  10.   Invalidate(FALSE);  
  11.  }  
  12. //CTreeCtrl::OnMouseMove(nFlags, point);  
  13. }  
  14.   
  15. 接下来我们通过获取当前绘制的节点和m_MouseMoveItem比对,如果相同则设置当前的背景颜色,从而实现热点时间  
  16.   
  17. //绘制鼠标热点  
  18.    if (currentItem==m_MouseMoveItem&&ItemHasChildren(currentItem)==NULL)  
  19.    {  
  20.     m_Gdiplus.usFillRectangle(pDc->m_hDC,fillRect,0xB7F0FE,0xB7F0FE,edoVertical,true);  
  21.    }  
  22.   
  23. //通过获取当前状态来判断单击事件。  
  24.   
  25.    if(itemState&TVIS_SELECTED)  
  26.    {  
  27.     m_Gdiplus.usFillRectangle(pDc->m_hDC,fillRect,0xFF00BB,0xFF00BB,edoVertical,true);  
  28.   
  29.    }  
  30. <p>这2句代码都在DrawItem函数中。</p>  

 

 之后在我们的一些常见操作之后触发WN_PAINT就可以了。

 这种重写ON_PAINT消息实现自绘的方法就是这些了,有没有觉得比我们前面的那种方法要简单好理解一些了。下面说说我为什么选择这种方法而不 用上面一种方法的原因,首先ON_PAINT可以获取到树形控件的整个DC,感觉绘制的时候方便一些,我想绘制到什么地方就绘制到什么地方,容易控制。其 次是这种方法需要掌握的数据结构比较的少就需要知道几个常见的函数就OK了。

 

需要掌握的函数:

CTreeCtrl::GetFirstVisibleItem
HTREEITEM GetFirstVisibleItem( );
返回值:如果成功则返回第一个可视项的句柄;否则返回NULL。
说明:
此成员函数用来获取该tree view控件中的第一个可视项的句柄。

CTreeCtrl::GetNextVisibleItem
HTREEITEM GetNextVisibleItem( HTREEITEM hItem );
返回值:返回下一个可视项的句柄;否则返回NULL。
参数:

 

hItem 一个tree项的句柄。

说明:此成员函数用来获取hItem的下一个可视项。

 CTreeCtrl::ItemHasChildren
BOOL ItemHasChildren( HTREEITEM hItem );
返回值:
如果由hItem指定的tree项有子项则返回非零值;否则返回0。

参数:

 

hItem 一个tree项的句柄。

 CTreeCtrl::GetItemText
CString GetItemText( HTREEITEM hItem ) const;
返回值:返回一个包含该项的文本的CString对象。
参数:

 

hItem 要获取其文本的项的句柄。

说明:
此成员函数返回由hItem指定的项的文本。

 CTreeCtrl::GetItemRect
BOOL GetItemRect( HTREEITEM hItem, LPRECT lpRect, BOOL bTextOnly );
返回值:
如果项是可视的则返回非零值,以及包含在lpRect中的边界矩形。否则,返回0和没有被初始化的lpRect。
参数:

hItem 一个tree view项的句柄。
lpRect 指向一个用来接收边界矩形的RECT结构的指针。其中的坐标是相对于该tree view控件的左上角的。
bTextOnly

如果这个参数是非零值,则边界矩形值包括项的文本。否则,它包括该项在tree view控件所占据的整个一行。

 

 

 

 

摘于: http://blog.csdn.net/wojiushi3344/article/details/7463942

Android手机代码查询命令大全

*#*#4636#*#*
  显示手机信息、电池信息、电池记录、使用统计数据、WiFi 信息
*#*#7780#*#*
  重设为原厂设定,不会删除预设程序,及SD卡档案。
*2767*3855#
  重设为原厂设定,会删除SD卡所有档案。
*#*#34971539#*#*
  显示相机相机韧体版本,或更新相机韧体
*#*#7594#*#*
  当长按关机按钮时,会出现一个切换手机模式的窗口,包括: 静音模式、飞航模式及关机,你可以用以上代码,直接变成关机按钮。
*#*#273283*255*663282*#*#*
  开启一个能让你备份媒体文件的地方,例如相片、声音及影片等
*#*#197328640#*#*启动服务模式,可以测试手机部分设置及更改设定WLAN、 GPS 及蓝牙测试的代码
*#*#232338#*#* 显示 WiFi MAC 地址
*#*#1472365#*#* GPS 测试
*#*#1575#*#* 其它 GPS 测试
*#*#232331#*#* 蓝牙测试
*#*#232337#*# 显示蓝牙装置地址
*#*#8255#*#*启动 GTalk 服务监视器
显示手机软件版本的代码:
*#*#1234#*#* PDA 及 Phone
*#*#1111#*#* FTA SW 版本
*#*#2222#*#* FTA HW 版本
各项硬件测试
*#*#0283#*#* Packet Loopback
*#*#0*#*#* LCD 测试
*#*#0842#*#* 装置测试,例如振动、亮度
*#*#2663#*#* 触控屏幕版本
*#*#2664#*#*触控屏幕测试
*#*#0588#*#* 接近感应器测试
*#*#3264#*#* 内存版本
*#06#显示手机原厂出厂串号(移动通信国际识别码)

 

 

摘于: http://bbs.anzhi.com/thread-7749842-1-1.html

Gentoo下emerge用法

避免升级覆盖掉版本更高的软件

emerge -uU world
emerge --update --upgradeonly world

查找名称包含mozilla的包

emerge -s mozilla
emerge search mozilla

查找描述包含mozilla

emerge -S mozilla
emerge --searchdesc mozilla

使用本地编好的包,没有就下源码(尽量避免编译)

emerge -k mozilla
emerge --usepkg mozilla

只使用本地编好的,否则不安装(绝对不编译,所有依赖的包都有binary才装)

emerge -K mozilla
emerge --usepkgonly mozilla

卸载
emerge -C mozilla
emerge unmerge mozilla

升级portage树

emerge sync

下载snapshot包来完成sync
emerge-webrsync

查看已安装包的changelog

emerge -pl mozilla
emerge --pretend --changelog mozilla

查看依赖关系(这个包还没装)
(--pretend保证这一次操作实际上不做任何事情,可以跟任何options组合)
emerge -p mozilla
emerge --pretend mozilla

只下载某个软件的源码(以及它所依赖的)

emerge -f mozilla
emerge --fetchonly mozilla

查看从哪下的源码

emerge -fp mozilla

安装指定版本号的

emerge "<mozilla-1.6"
emerge "=..........."
.......">..........."

emerge -k "<mozilla-1.6".....


从网上下binary包来装

emerge -g mozilla
emerge --getbinpkg mozilla
(注意,实际上没有任何binary包存在于官方的mirror中
所以这个基本上是无用,在manpage也没有出现。除非自
己用livecd来setup一个这样的站点。不知道以后会不会
出现这样的mirror。gentoo.org论坛上似乎也有讨论这个。)

查看binary包依赖

emerge -gp mozilla
emrege --getbinpkg --pretend mozilla


查看依赖关系(这个包已经装了)

emerge -ep opera
emerge --emptytree --pretend opera
(不用pretend会重新编译这所有依赖的包,glibc因为安全关系没有列出)

不使用依赖关系安装软件

emerge -O opera
emerge --nodeps opera

只安装其依赖的软件
emerge -o opera
emerge --onlydeps opera

升级软件
emerge -u opera
emerge --update opera

升级系统软件

emerge -u system

升级整个系统

emerge -u world

避免升级覆盖掉版本更高的软件

emerge -uU world
emerge --update --upgradeonly world

查看可用的USE参数

emerge -pv opera

 

 

摘于:http://blog.csdn.net/zhuyingqingfen/article/details/6825647

Gentoo的emerge命令参数用法详解

Gentoo的包管理工具称为portage。emerge是这个portage的字符界面管理工具,图形界面工具还有portato,porthole,kuroo,himerge等。

ebuild

ebuild是Portage包管理程序的根本。它是一个纯文本文件,而每一个ebuild都会对应一个包(软件包)。ebuild会告诉 portage要下载的文件、该包可运行的平台、如何编译它、它所依赖的ebuild和一些修补代码的patch。Portage内有一个ebuild大 集合,称为Portage tree,是gentoo网站所提供的ebuild。它包含了大部份常用的包,并会不时更新。如果要使用的包不在其内,也可以手动加入。

USE标志

USE标志的设置位于Gentoo系统的/etc/make.conf文档中,作用是使得Emerge在处理依赖关系的时候可以做到不安装不需要的软件包(例如安装Gnome的用户没有必要因为一个软件包的依赖关系而安装KDE与Qt),而安装指定的软件包(同样以Gnome举例,Gnome的用户基本上都会安装GTK+),把系统的设置专注化。

Gentoo的emerge命令参数用法详解

查找名称包含mozilla的包

emerge -s mozilla
emerge search mozilla


查找描述包含mozilla

emerge -S mozilla
emerge --searchdesc mozilla


使用本地编好的包,没有就下源码(尽量避免编译)

emerge -k mozilla
emerge --usepkg mozilla


只使用本地编好的,否则不安装(绝对不编译,所有依赖的包都有binary才装)

emerge -K mozilla
emerge --usepkgonly mozilla


卸载

emerge -C mozilla
emerge unmerge mozilla


升级portage树

emerge --sync

下载snapshot包来完成sync

emerge-webrsync

查看已安装包的changelog

emerge -pl mozilla
emerge --pretend --changelog mozilla


查看依赖关系(这个包还没装)
(–pretend保证这一次操作实际上不做任何事情,可以跟任何options组合)

emerge -p mozilla
emerge --pretend mozilla


只下载某个软件的源码(以及它所依赖的)

emerge -f mozilla
emerge --fetchonly mozilla


查看从哪下的源码

emerge -fp mozilla

安装指定版本号的

emerge "..........."

emerge -k "

从网上下binary包来装

emerge -g mozilla
emerge --getbinpkg mozilla


(注意,实际上没有任何binary包存在于官方的mirror中
所以这个基本上是无用,在manpage也没有出现。除非自
己用livecdsetup一个这样的站点。不知道以后会不会
出现这样的mirror。gentoo.org论坛上似乎也有讨论这个。)

查看binary包依赖

emerge -gp mozilla
emrege --getbinpkg --pretend mozilla


查看依赖关系(这个包已经装了)

emerge -ep opera
emerge --emptytree --pretend opera


(不用pretend会重新编译这所有依赖的包,glibc因为安全关系没有列出)

不使用依赖关系安装软件

emerge -O opera
emerge --nodeps opera


只安装其依赖的软件

emerge -o opera
emerge --onlydeps opera


升级软件

emerge -u opera
emerge --update opera


升级系统软件

emerge -u system

升级整个系统

emerge -u world

避免升级覆盖掉版本更高的软件

emerge -uU world
emerge --update --upgradeonly world


查看可用的USE参数
emerge -pv opera

参考文档:http://www.gentoo.org/doc/zh_cn/index.xml

: http://www.ha97.com/3192.html

 

 

 

摘于: http://www.ha97.com/3192.html

Gentoo老版本升级过程emerge异常报错处理

  • Gentoo老版本升级过程emerge异常报错处理

 

 

  • 最近升级一个比较老版本的服务器,全面升级时,出现各种问题,下面记录一下,和大家分享。
  • 交叉依赖

一种方法是一起安装。

emerge -aDuv portage 时,报警和 logrotate 冲突,一起安装就可以了。

emerge -a1 portage logrotate

一种是先强制安装一个。

# emerge -uDv world时,gccglibc之前交叉依赖。一起装也不行,就先强制安装gcc

# emerge -uDv --no-deps gcc

 

  • 被阻挡的包

[blocks B] 方法可以是卸载阻挡的包;或者按照阻挡的包的新版本;

比如报错: [blocks B]    A is bocking B

# emerge --unmerge A

 

  • no ebuilds报错

删除一些软件后,有时会出现以下的报错:

emerge: there are no ebuilds to satisfy "media-fonts/gnu-gs-fonts-std".

这时候,可以考虑继续把这个软件删除。

 

  • 编译时库版本低报错

安装的编译过程中有时候会报错,某些库版本太低了。

这时候就需要先安装这个库。可能需要重新编译受影响的包,出现问题不要紧张,继续即可。

 

  • IOError报错

有时候会安装时报错:

IOError: [Errno 11] Resource temporarily unavailable

是个python执行的错误,可能是emerge程序的问题,使用新版本或其他机器上好用的版本。或者先升级emerge

# emerge -uDv emerge

 

  • USE报错

有时候会报错 USE changes ,有些包需要特定的USE,没有设置,可以在/etc/portage/package.use 中设置。

 

请仔细查看emerge的最后的输出提示。按提示进行相应的操作。

 

  • 网络异常

升级过程中还遇到一个问题:配置网络后,apache无法启动,说启动eth1,这个设备根本不存在。检查eth0设备正常,能够连接外网,但服务都企图启动eth1,然后失败,不能启动。报错类似:

# /etc/init.d/apache start

    Bringing up interface eth1

  ERROR: interface eth1 does not exist

发现 /etc/init.d 只有net.eth1的符号连接,没有net.eth0的符号连接,建立连接

Ln-s /etc/init.d/net.lo /etc/init.d/net.eth0

这样还是不正常,删除/etc/init.d/net.eth1 后恢复正常。

 

 

 

 

摘于:http://blog.chinaunix.net/uid-26880392-id-3191434.html

 

Virtualbox的物理硬盘分区加载与使用

sudo chmod o+rw /dev/sdb2

 

VBoxManage internalcommands createrawvmdk -filename sdb3.vmdk -rawdisk /dev/sdb2

 

 

 

摘于: http://biolinux.cqmu.edu.cn/zh/modules/news/view.article.php?category=2&article=14&page=1

不能为虚拟电脑 xxx 打开一个新任务. Failed to load VMMR0.r0 (VERR_SUPLIB_OWNER_NOT_ROOT). 返回 代码:NS_ERROR_FAIL

转自:     http://blog.csdn.net/fjb2080/article/details/8875322

以及 :    http://blog.163.com/vic_kk/blog/static/4947052420112844544747/

 

描述:

不能为虚拟电脑 xxx 打开一个新任务.

Failed to load VMMR0.r0 (VERR_SUPLIB_OWNER_NOT_ROOT).

返回 代码:NS_ERROR_FAILURE (0x80004005)

组件:Console

界面:IConsole {db7ab4ca-2a3f-4183-9243-c1208da92392}

 

这是irtualbox报的一个错误。

 

解决办法:

ls -ld /usr       /usr/lib              (注意:  这是两个目录)

看下其所有者是否位root:root。如果不是,执行下面命令:

sudo chown root: root /usr    /usr/lib

 

 

也有可能是因为 /opt 的 owner 不是 root 导致的,将其 owner 改成 root 即可:

 

sudo chown root /opt

 

再次运行Virtualbox就ok了。

 

 

 

摘于: http://blog.csdn.net/a593796769/article/details/8969988

virtualbox com 错误

编写一个脚本

 

#!/bin/bash
cd /opt/VirtualBox/
LD_LIBRARY_PATH=. ./VBoxSVC &
virtualbox

 

再运行这个脚本试试看。

 

 

 

摘于: http://www.linuxdiyf.com/viewarticle.php?id=45106