网上找了很多
,
可只是给出代码
,
没有详细解释
,
不便初学者理解
.
我就抄回冷饭
.
把这个再拿出来说说
.
实例图片
:
首先建立一个标准的
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");
if (onlywin)
{
ExitProcess(1);
}
接下来创建主窗口
hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);
需要注意的是我们这里并不调用ShowWindow()和UpdateWindow();因为我们不需要显示主窗口
hDlgNow = DoMainDlg(hMainWnd);
ShowWindow(hDlgNow, nCmdShow);
这里调用
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))
{
if (NULL == hDlgNow || !IsDialogMessage(hDlgNow, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
其中
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,
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)
//
如果按钮被禁用
{
OffsetRect(&captionRect, 1, 1);
SetTextColor(dc, ::GetSysColor(COLOR_3DHILIGHT));
DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
OffsetRect(&captionRect, -1, -1);
SetTextColor(dc, ::GetSysColor(COLOR_3DSHADOW));
DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
}
else
//
如果没被禁用正常画
{
SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));
SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));
DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
}
}
//
画按钮得到焦点时的虚线方框
if
(bIsFocused && bDrawFocusRect)
{
RECT focusRect = itemRect;
InflateRect(&focusRect, -3, -3);
DrawFocusRect(dc, &focusRect);
}
// if
return
(TRUE);
}
break
;
到此
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,
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);
break;
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;