VC++ WIN32 sdk实现按钮自绘详解.

http://blog.csdn.net/geniusdot/article/details/1520058

 

网上找了很多 , 可只是给出代码 , 没有详细解释 , 不便初学者理解 . 我就抄回冷饭 . 把这个再拿出来说说 .
实例图片 :
首先建立一个标准的 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;
本文部分内容参考自 http://www.codeproject.com/buttonctrl/nativewin32xpthemes.asp
本实例源代码 http://geniusdot.googlepages.com/new.rar

 

你可能感兴趣的:(VC++ WIN32 sdk实现按钮自绘详解.)