您的位置:首页 > 编程语言 > C语言/C++

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

2014-04-30 16:23 417 查看
[align=left]网上找了很多,可只是给出代码,没有详细解释,不便初学者理解.我就抄回冷饭.把这个再拿出来说说.[/align]
[align=left]实例图片:[/align]



[align=left]首先建立一个标准的Win32 Application 工程.选择a simple Win32 Application.[/align]
[align=left]然后建立我们的资源文件首先新建一个对话框资源,资源ID改为IDD_MAIN_DLG[/align]
[align=left]然后在其上新建一个按钮控件资源ID改为IDC_ODBUTTON,此按钮的styles中必须选中owenerdraw属性.[/align]
[align=left]然后将其保存为.rc的资源文件.并将其导入我们的工程.同理新建一个图标文件资源ID改为IDI_OWNERDRAW保存为.ico的图标然后导入.[/align]
[align=left]准备工作做完了下面开始写代码.[/align]
[align=left]首先声明如下全局变量.[/align]
[align=left]#include "stdafx.h"
#include "resource.h"
[/align]
[align=left] HINSTANCE odInst = NULL; //接收程序实例的句柄
HWND hMainWnd = NULL; //接收主窗口的句柄
HWND hDlgNow = NULL; //接收对话框的句柄
static HICON hOwnerDrawIcon = NULL; //用作自绘按钮的图标
static LONG prev_proc; //储存按钮先前的回调函数
static HICON hIcon = NULL; //对话框图标句柄
[/align]
[align=left]然后开始写WinMain()函数[/align]
[align=left]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;
}
[/align]
[align=left] [/align]
[align=left]首先注册一个标准的窗口类,的WNDCLASS结构体,默认的窗口过程为ODWndProc.其定义如下.[/align]
[align=left]LRESULT CALLBACK ODWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);//返回系统默认的窗口过程
}
[/align]
[align=left] 然后判断有无相同实例存在如有则结束之[/align]
[align=left]HWND onlywin= FindWindow("OwnerDraw","MyOwnerDraw");

if (onlywin)
{
ExitProcess(1);
}
[/align]
[align=left] [/align]
[align=left]接下来创建主窗口[/align]
[align=left]hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);
[/align]
[align=left]需要注意的是我们这里并不调用ShowWindow()和UpdateWindow();因为我们不需要显示主窗口[/align]
[align=left] [/align]
[align=left]hDlgNow = DoMainDlg(hMainWnd);
ShowWindow(hDlgNow, nCmdShow);
[/align]
[align=left]这里调用DoMainDlg函数创建一个对话框并显示之. DoMainDlg函数实现如下.[/align]
[align=left]HWND DoMainDlg(HWND parent)
{
DWORD dwErr;
HWND hRet = CreateDialog(odInst, (LPCTSTR)IDD_MAIN_DLG, parent, (DLGPROC)MainDlgProc);
if(hRet == NULL)
dwErr = GetLastError();

return hRet;

}
[/align]
[align=left] [/align]
[align=left]最后为消息循环[/align]
[align=left]while(GetMessage(&msg, NULL, 0, 0))
{
if (NULL == hDlgNow || !IsDialogMessage(hDlgNow, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
[/align]
[align=left] [/align]
[align=left]其中IsDialogMessage(hDlgNow, &msg)主要作用是把指定对话框的消息,路由给其处理.[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]下面是对话框窗口的默认消息响应回调函数MainDlgProc[/align]
[align=left]我这里主要讲响应WM_DRAWITEM消息与WM_INITDIALOG..[/align]
[align=left]首先是响应WM_INITDIALOG[/align]
[align=left]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;
[/align]
[align=left] [/align]
[align=left]首先为对话框加载一个图标,我这里图省事全部都用了一个图标,在实际应用中.可以随需要更换.[/align]
[align=left]然后是为自绘按钮加载图标.接下来改变默认的自绘按钮的窗口过程.将原按钮过程存与prev_proc中.[/align]
[align=left]最后发送WM_SETICON消息设置对话框图标和设置焦点.[/align]
[align=left] [/align]
[align=left]接下来是响应WM_DRAWITEM消息,需要说明的是这个消息必须要设置了BS_OWNERDRAW[/align]
[align=left]我们用记事本打开我们的对话框资源文件会看到类似下面的设置[/align]
[align=left]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
[/align]
[align=left] [/align]
[align=left]此处资源文件中的BS_OWNERDRAW即对应创建按钮时选中的Ownerdraw属性.之所以这样作是因为只有这样[/align]
[align=left]对话框才能响应WM_DRAWITEM消息.下面为代码.[/align]
[align=left] [/align]
[align=left]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;
[/align]
[align=left] [/align]
[align=left]到此WM_DRAWITEM消息响应完毕.下面我们看看DrawTheIcon这个函数.[/align]
[align=left]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);
}
[/align]
[align=left]还有其中的PrepareImageRect函数[/align]
[align=left] [/align]
[align=left]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);

}
[/align]
[align=left] [/align]
[align=left]行了到这里主要的工作都作完了还要说明的就是ButtWindProc这个按钮窗口的回调函数.写它的主要目的是为了[/align]
[align=left]实现按钮的连续单击,在此函数中我们处理了WM_LBUTTONDBLCLK鼠标双击事件,并将其转化为一个单击事件.像这样:[/align]
[align=left]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);

}
[/align]
[align=left] [/align]
[align=left]下面只需再响应WM_SETICON, WM_SYSCOMMAND, WM_COMMAND.这三个没什么好说的.前两个交由系统默认过程处理,最后一个对应对话框上的确定和取消,都是销毁窗口的行为.[/align]
[align=left]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;
[/align]
[align=left]本文部分内容参考自http://www.codeproject.com/buttonctrl/nativewin32xpthemes.asp[/align]
[align=left]本实例源代码http://geniusdot.googlepages.com/new.rar[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: