自绘控件的4种方法
本篇文章主要介紹了"自繪控件的4種方法",主要涉及到自繪控件的4種方法方面的內容,對于自繪控件的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。
總結
- 上一篇: java会被rust替代吗_自从尝了 R
- 下一篇: VC控件自绘制三步曲