網上找了很多,可隻是給出代碼,沒有詳細解釋,不便初學者了解.我就抄回冷飯.把這個再拿出來說說.
執行個體圖檔:

首先建立一個标準的Win32 Application 工程.選擇a simple Win32 Application.
然後建立我們的資源檔案首先建立一個對話框資源,資源ID改為IDD_MAIN_DLG
然後在其上建立一個按鈕控件資源ID改為IDC_ODBUTTON,此按鈕的styles中必須選中owenerdraw屬性.
然後将其儲存為.rc的資源檔案.并将其導入我們的工程.同理建立一個圖示檔案資源ID改為IDI_OWNERDRAW儲存為.ico的圖示然後導入.
準備工作做完了下面開始寫代碼.
首先聲明如下全局變量.
#include "stdafx.h"
#include "resource.h"
HINSTANCE odInst = NULL; //接收程式執行個體的句柄
HWND hMainWnd = NULL; //接收主視窗的句柄
HWND hDlgNow = NULL; //接收對話框的句柄
static HICON hOwnerDrawIcon = NULL; //用作自繪按鈕的圖示
static LONG prev_proc; //儲存按鈕先前的回調函數
static HICON hIcon = NULL; //對話框圖示句柄
然後開始寫WinMain()函數
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
odInst = hInstance;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)ODWndProc; //定義一個視窗預設函數,這裡我們會交由預設視窗函數處理
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(odInst,MAKEINTRESOURCE(IDI_OWNERDRAW));
wc.hCursor = NULL;
wc.hbrBackground = 0;
wc.lpszClassName = "OwnerDraw";
wc.lpszMenuName = NULL;
RegisterClass(&wc);
MSG msg;
HWND onlywin= FindWindow("OwnerDraw","MyOwnerDraw");
if (onlywin)
{
ExitProcess(1);
}
hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);
if (!hMainWnd)
return FALSE;
hDlgNow = DoMainDlg(hMainWnd);
ShowWindow(hDlgNow, nCmdShow);
while(GetMessage(&msg, NULL, 0, 0))
{
if (NULL == hDlgNow || !IsDialogMessage(hDlgNow, &msg))
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
首先注冊一個标準的視窗類,的WNDCLASS結構體,預設的視窗過程為ODWndProc.其定義如下.
LRESULT CALLBACK ODWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hWnd, message, wParam, lParam);//傳回系統預設的視窗過程
然後判斷有無相同執行個體存在如有則結束之
HWND onlywin= FindWindow("OwnerDraw","MyOwnerDraw");
接下來建立主視窗
hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
需要注意的是我們這裡并不調用ShowWindow()和UpdateWindow();因為我們不需要顯示主視窗
hDlgNow = DoMainDlg(hMainWnd);
這裡調用DoMainDlg函數建立一個對話框并顯示之. DoMainDlg函數實作如下.
HWND DoMainDlg(HWND parent)
DWORD dwErr;
HWND hRet = CreateDialog(odInst, (LPCTSTR)IDD_MAIN_DLG, parent, (DLGPROC)MainDlgProc);
if(hRet == NULL)
dwErr = GetLastError();
return hRet;
最後為消息循環
while(GetMessage(&msg, NULL, 0, 0))
其中IsDialogMessage(hDlgNow, &msg)主要作用是把指定對話框的消息,路由給其處理.
下面是對話框視窗的預設消息響應回調函數MainDlgProc
我這裡主要講響應WM_DRAWITEM消息與WM_INITDIALOG..
首先是響應WM_INITDIALOG
case WM_INITDIALOG:
if(hIcon == NULL)
hIcon = LoadIcon(odInst, MAKEINTRESOURCE(IDI_OWNERDRAW));
if(hOwnerDrawIcon == NULL)
hOwnerDrawIcon = (HICON)LoadImage(odInst,
MAKEINTRESOURCE(IDI_OWNERDRAW),
IMAGE_ICON,
38,
0);
prev_proc = SetWindowLongPtr(GetDlgItem(hDlg, IDC_ODBUTTON), GWLP_WNDPROC, (LONG)ButtWindProc);
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SetFocus(GetDlgItem(hDlg, IDC_ODBUTTON));
break;
首先為對話框加載一個圖示,我這裡圖省事全部都用了一個圖示,在實際應用中.可以随需要更換.
然後是為自繪按鈕加載圖示.接下來改變預設的自繪按鈕的視窗過程.将原按鈕過程存與prev_proc中.
最後發送WM_SETICON消息設定對話框圖示和設定焦點.
接下來是響應WM_DRAWITEM消息,需要說明的是這個消息必須要設定了BS_OWNERDRAW
我們用記事本打開我們的對話框資源檔案會看到類似下面的設定
IDD_MAIN_DLG DIALOG DISCARDABLE 0, 0, 250, 142
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 10, "System"
BEGIN
DEFPUSHBUTTON "OK",IDOK,193,7,50,14
PUSHBUTTON "Cancel",IDCANCEL,193,24,50,14
CONTROL "OwnerDraw",IDC_ODBUTTON,"Button",BS_OWNERDRAW |
WS_TABSTOP,49,31,79,26
END
此處資源檔案中的BS_OWNERDRAW即對應建立按鈕時選中的Ownerdraw屬性.之是以這樣作是因為隻有這樣
對話框才能響應WM_DRAWITEM消息.下面為代碼.
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT) lParam;
//聲明一個指向DRAWITEMSTRUCT結構體的指針并将其指向存儲着按鈕構造資訊的lParam
if(lpDIS->CtlID != IDC_ODBUTTON)
return (0);
HDC dc = lpDIS->hDC; //用于按鈕繪制的DC
BOOL bIsPressed = (lpDIS->itemState & ODS_SELECTED);
BOOL bIsFocused = (lpDIS->itemState & ODS_FOCUS);
BOOL bIsDisabled = (lpDIS->itemState & ODS_DISABLED);
BOOL bDrawFocusRect = !(lpDIS->itemState & ODS_NOFOCUSRECT);
//判斷按鈕各種狀态的BOOL值
RECT itemRect = lpDIS->rcItem; //按鈕的矩形區域
SetBkMode(dc, TRANSPARENT); //設定繪制按鈕時的背景狀态
if (bIsFocused) //判斷按鈕是否獲得了焦點并對其邊框進行處理
{
HBRUSH br = CreateSolidBrush(RGB(0,0,0));
FrameRect(dc, &itemRect, br);
InflateRect(&itemRect, -1, -1);
DeleteObject(br);
} // if
COLORREF crColor = GetSysColor(COLOR_BTNFACE);//得到系統按鈕顔色
HBRUSH brBackground = CreateSolidBrush(crColor);//建立畫刷
FillRect(dc, &itemRect, brBackground);//繪制按鈕
DeleteObject(brBackground);
// 這裡畫被按下去的按鈕
if (bIsPressed)
HBRUSH brBtnShadow = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
FrameRect(dc, &itemRect, brBtnShadow);
DeleteObject(brBtnShadow);
}
else //如果沒有被按下就這樣畫
UINT uState = DFCS_BUTTONPUSH |
((bIsPressed) ? DFCS_PUSHED : 0);
DrawFrameControl(dc, &itemRect, DFC_BUTTON, uState);
char sTitle[100];
GetWindowText(GetDlgItem(hDlg, IDC_ODBUTTON), sTitle, 100);//得到按鈕的文本
RECT captionRect = lpDIS->rcItem;//把文本的區域設定為按鈕區域
BOOL bHasTitle = (sTitle[0] !='/0');//按鈕上是否有文本存在
//這裡畫按鈕上的圖示,具體實作見下面
(GetDlgItem(hDlg, IDC_ODBUTTON), &dc, bHasTitle,
&lpDIS->rcItem, &captionRect, bIsPressed, bIsDisabled);
if (bHasTitle)//如果按鈕有文本标題
// 按鈕被按下的處理
if (bIsPressed)
OffsetRect(&captionRect, 1, 1);
// 将文本居中
RECT centerRect = captionRect;
DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CALCRECT|DT_CENTER);
LONG captionRectWidth = captionRect.right - captionRect.left;
LONG captionRectHeight = captionRect.bottom - captionRect.top;
LONG centerRectWidth = centerRect.right - centerRect.left;
LONG centerRectHeight = centerRect.bottom - centerRect.top;
OffsetRect(&captionRect, (centerRectWidth - captionRectWidth)/2, (centerRectHeight - captionRectHeight)/2);
SetBkMode(dc, TRANSPARENT);
if (bIsDisabled)//如果按鈕被禁用
{
SetTextColor(dc, ::GetSysColor(COLOR_3DHILIGHT));
DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
OffsetRect(&captionRect, -1, -1);
SetTextColor(dc, ::GetSysColor(COLOR_3DSHADOW));
}
else //如果沒被禁用正常畫
SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));
SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));
// 畫按鈕得到焦點時的虛線方框
if (bIsFocused && bDrawFocusRect)
RECT focusRect = itemRect;
InflateRect(&focusRect, -3, -3);
DrawFocusRect(dc, &focusRect);
} // if
return (TRUE);
}
到此WM_DRAWITEM消息響應完畢.下面我們看看DrawTheIcon這個函數.
static void DrawTheIcon(HWND hButtonWnd, HDC* dc, BOOL bHasTitle, RECT* rpItem, RECT* rpTitle, BOOL bIsPressed, BOOL bIsDisabled)
RECT rImage;
PrepareImageRect(hButtonWnd, bHasTitle, rpItem, rpTitle, bIsPressed, 38, 38, &rImage);
// 調用API函數按準備好的形式将圖檔畫到按鈕上
DrawState( *dc,
NULL,
(LPARAM)hOwnerDrawIcon,
0,
rImage.left,
rImage.top,
(rImage.right - rImage.left),
(rImage.bottom - rImage.top),
(bIsDisabled ? DSS_DISABLED : DSS_NORMAL) | DST_ICON);
還有其中的PrepareImageRect函數
static void PrepareImageRect(HWND hButtonWnd, BOOL bHasTitle, RECT* rpItem, RECT* rpTitle, BOOL bIsPressed, DWORD dwWidth, DWORD dwHeight, RECT* rpImage)
RECT rBtn;
CopyRect(rpImage, rpItem);
GetClientRect(hButtonWnd, &rBtn);
if (bHasTitle == FALSE)//如果按鈕上有文本内容
// 使圖檔水準居中
LONG rpImageWidth = rpImage->right - rpImage->left;
rpImage->left += ((rpImageWidth - (long)dwWidth)/2);
else
{ //控制圖檔與焦點方框内部
LONG rpTitleWidth = rpTitle->right - rpTitle->left;
rpTitle->right = rpTitleWidth - dwWidth - 30;
rpTitle->left = 30;
rpImage->left = rBtn.right - dwWidth - 22;
LONG rpImageHeight = rpImage->bottom - rpImage->top;
rpImage->top += ((rpImageHeight - (long)dwHeight)/2);
if (bIsPressed)//按鈕被按下的處理
OffsetRect(rpImage, 1, 1);
行了到這裡主要的工作都作完了還要說明的就是ButtWindProc這個按鈕視窗的回調函數.寫它的主要目的是為了
實作按鈕的連續單擊,在此函數中我們處理了WM_LBUTTONDBLCLK滑鼠輕按兩下事件,并将其轉化為一個單擊事件.像這樣:
LRESULT CALLBACK ButtWindProc(
HWND hWnd, //window handle
UINT message, // type of message
WPARAM wParam, // additional information
LPARAM lParam) //additional information
switch (message)
case WM_LBUTTONDBLCLK:
PostMessage(hWnd, WM_LBUTTONDOWN, wParam, lParam);
break;
//将不做處理的消息路由給原預設函數
return CallWindowProc((WNDPROC)prev_proc, hWnd, message, wParam, lParam);
下面隻需再響應WM_SETICON, WM_SYSCOMMAND, WM_COMMAND.這三個沒什麼好說的.前兩個交由系統預設過程處理,最後一個對應對話框上的确定和取消,都是銷毀視窗的行為.
case WM_SETICON:
DefWindowProc(hDlg, message, wParam, lParam);
case WM_SYSCOMMAND:
return DefWindowProc(hDlg, message, wParam, lParam);
case WM_COMMAND:
switch (LOWORD(wParam))
case IDCANCEL:
case IDOK:
DestroyIcon(hOwnerDrawIcon);
PostQuitMessage(0);
return TRUE;
}//switch
break;