自绘按钮的实现并不难,只不过如果是在非MFC类库中实现,却是显得有点麻烦,或是说,更需要一点小技巧.
文章打算在CMainWnd窗口类中实现一个自绘按钮,为了方便讲解以及突出重点,CMainWnd直接派生于CWndBase类.关于CWndBase类的信息,可以在此找到:http://blog.csdn.net/norains/archive/2008/01/12/2040109.aspx
首先我们先要创建一个按钮,我将它命名为m_hBnExit:
m_hBnExit = CreateWindowEx(WS_EX_TOPMOST,
TEXT("BUTTON"),
TEXT(""),
BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW ,
POS_EXIT.left,
POS_EXIT.top,
POS_EXIT.right - POS_EXIT.left,
POS_EXIT.bottom - POS_EXIT.top,
m_hWnd,
(HMENU)IDC_BTN_EXIT,
m_hInst,
NULL);
这里有点需要注意,因为我们需要重新绘制按钮,所以BS_OWNERDRAW风格一定需要设置.一旦设置该风格,CMainWnd窗口的消息处理函数就能接收到按钮的WM_DRAWITEM消息:
LRESULT CMainWnd::WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
...
switch(wMsg)
{
case WM_DRAWITEM:
if(((LPDRAWITEMSTRUCT)lParam)->CtlID == IDC_BTN_EXIT)
{
//TODO something;
}
...
}
...
}
接下来需要做的就很简单了,我们只需要在接收到WM_DRAWITEM消息时,判断是否为退出按键的ID号(在本文例子中为IDC_BTN_EXIT),如果是的话就重新绘制.为此,我们定义一个OnDrawItemBtnExit()函数,用来绘制该按钮:
void CMainWnd::OnDrawItemBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
//读取按钮的图片
HANDLE hBmp = LoadImage(m_hInst,MAKEINTRESOURCE(IDB_EXIT),IMAGE_BITMAP,0,0,0);
if(hBmp == NULL)
{
return ;
}
//Draw the button
HDC hdcBmp = CreateCompatibleDC(lpdis->hDC);
HGDIOBJ hOldSel = SelectObject(hdcBmp,hBmp);
if((lpdis->itemState & ODS_SELECTED ) && !(lpdis->itemState & ODS_DISABLED))
{
//按下状态
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_PUSH.left,
IMG_EXIT_PUSH.top,
IMG_EXIT_PUSH.right - IMG_EXIT_PUSH.left,
IMG_EXIT_PUSH.bottom - IMG_EXIT_PUSH.top,
DEFAULT_TRANSPARENT_COLOR);
}
else if(lpdis->itemState & ODS_DISABLED)
{
//无效状态
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_DISABLE.left,
IMG_EXIT_DISABLE.top,
IMG_EXIT_DISABLE.right - IMG_EXIT_DISABLE.left,
IMG_EXIT_DISABLE.bottom - IMG_EXIT_DISABLE.top,
DEFAULT_TRANSPARENT_COLOR);
}
else
{
//正常状态
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_ENABLE.left,
IMG_EXIT_ENABLE.top,
IMG_EXIT_ENABLE.right - IMG_EXIT_ENABLE.left,
IMG_EXIT_ENABLE.bottom - IMG_EXIT_ENABLE.top,
DEFAULT_TRANSPARENT_COLOR);
}
if(hdcBmp != NULL && hOldSel != NULL)
{
SelectObject(hdcBmp,hOldSel);
DeleteDC(hdcBmp);
hdcBmp = NULL;
hOldSel = NULL;
}
if(hBmp != NULL)
{
DeleteObject(hBmp);
hBmp = NULL;
}
}
不过这么一来会有个很明显的问题,如图1:
我们发现在按钮周围有一圈非常不雅的灰色地带,而这灰色区域是窗口创建的时候自绘的.有经验的朋友都知道,如果是自己创建的窗口,要让这灰色区域消失是非常简单的事情,只需要在调用RegisterClass函数时选择一个无颜色的画刷即可.但由于所有的Windows Control的注册函数都是封装的,我们无法对其参数进行变更,所以唯一的方法只能是另寻窍门.
所谓的去掉灰色地带,无非就是把主窗口那处被掩盖的区域重新绘制出来.有这个思路一切就好办了,我们只需要在主窗口的WM_PAINT响应函数中存储主窗口的DC数据,然后在按钮重绘的时候再把它显示出来即可:
void CMainWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
...
//存储主窗口的所有DC数据到m_hdcBufBk中
//Create the memory DC for storing the background.
m_hBmpBufBk = CreateCompatibleBitmap(hdc,rcWnd.right - rcWnd.left,rcWnd.bottom - rcWnd.top);
m_hdcBufBk = CreateCompatibleDC(hdc);
m_hOldSelBufBk = SelectObject(m_hdcBufBk,m_hBmpBufBk);
//Draw the background
HPEN hPen=CreatePen(PS_SOLID,1,RGB(0,255,0));
HPEN hOldPen=NULL;
hOldPen=(HPEN)SelectObject(m_hdcBufBk,hPen);
//the rect color
HBRUSH hBrush = CreateSolidBrush(RGB(0,255,0));
HGDIOBJ hOldBrush = SelectObject(m_hdcBufBk,hBrush);
//Draw
Rectangle(m_hdcBufBk,0,0,rcWnd.right - rcWnd.left + 1, rcWnd.bottom - rcWnd.top + 1);
//Realse the resource
SelectObject(m_hdcBufBk,hOldBrush);
DeleteObject(hBrush);
SelectObject(m_hdcBufBk,hOldPen);
DeleteObject(hPen);
...
}
然后在按钮的自绘函数中将其绘制出来:
void CMainWnd::OnDrawItemBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
...
//Draw the background
BitBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
m_hdcBufBk,
POS_EXIT.left,
POS_EXIT.top,
SRCCOPY);
...
}
添加背景绘制代码之后显示的效果如图2所示:
如果你的目的仅仅是用图片替代原来那种死板的按钮样式的话,那么到这步已经是大功告成,可以举杯欢庆了.但如果你还需要更多的需求,比如显示出通过SetWindowText设置的字符串,那么你还必须要做更多的工作.
因为我们无法直接通过GetWindowText获取设置文本的真正长度,所以我们必须在按钮接收WM_SETTEXT消息时获取窗口文本.也许有人说,我设置一个很大的缓冲区不就行了么?可是,你需要设置多大?最好不要抱这种想法,因为我们永远不知道用户的真正意思!
除了文档中明白指出的按钮消息,其它的消息我们都无法在MainProc函数中截获,比如按钮的WM_PAINT,WM_ERASEBKGND等.所以,这里我们需要在创建按钮控件的时候用SetWindowLong函数来转移按钮消息(关于SetWindowLong与消息转移可以参加我这篇文章:http://blog.csdn.net/norains/archive/2008/01/03/2023986.aspx).
//Store the pointer in the window
SetWindowLong(m_hBnExit, GWL_USERDATA, (DWORD)this);
//Set the new window procedure
m_PreProcBnExit = (WNDPROC)SetWindowLong(m_hBnExit,GWL_WNDPROC,(DWORD)CtrlProc);
我们需要在转移的消息处理函数中截获WM_SETTEXT消息,并分配合适的内存空间存储设置的字符串:
LRESULT CMainWnd::CtrlProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
CMainWnd *pObject = (CMainWnd *)GetWindowLong(hWnd,GWL_USERDATA);
if(pObject != NULL)
{
if(hWnd == pObject->m_hBnExit)
{
switch(wMsg)
{
case WM_SETTEXT:
{
if(pObject->m_pszBnExit != NULL)
{
delete []pObject->m_pszBnExit;
pObject->m_pszBnExit = NULL;
}
pObject->m_pszBnExit = new TCHAR [_tcslen( reinterpret_cast<</span>LPCTSTR>(lParam)) + 1];
_tcscpy(pObject->m_pszBnExit,reinterpret_cast<</span>LPCTSTR>(lParam));
break;
}
}
return CallWindowProc(pObject->m_PreProcBnExit,hWnd,wMsg,wParam,lParam);
}
}
return DefWindowProc(hWnd,wMsg,wParam,lParam);
}
既然我们已经获得了字符串,要绘制出来就是轻而易举的事情了:
void CMainWnd::OnDrawItemBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
...
//Draw the text
SetBkMode(lpdis->hDC,TRANSPARENT);
DrawText(lpdis->hDC,m_pszBnExit,-1,&lpdis->rcItem,DT_CENTER | DT_VCENTER);
...
}
该工程可以在此下载:http://download.csdn.net/source/333479
CMainWnd完整的代码如下:
#pragma once
#include "Wndbase.h"
class CMainWnd :
public CWndBase
{
public:
virtual BOOL Create(HINSTANCE hInst, HWND hWndParent, const TCHAR *pcszWndClass, const TCHAR *pcszWndName);
CMainWnd(void);
~CMainWnd(void);
protected:
virtual LRESULT WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
static LRESULT CtrlProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
void OnClickedBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
void OnDrawItemBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
void OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
private:
HDC m_hdcBufBk; //The dc buffer is just for storing the background data.
HBITMAP m_hBmpBufBk;
HGDIOBJ m_hOldSelBufBk;
HWND m_hBnExit;
TCHAR *m_pszBnExit;
WNDPROC m_PreProcBnExit;
};
#include "stdafx.h"
#include "resource.h"
#include "MainWnd.h"
//*****************************************************************************************************
//The default transparent color for the image
#define DEFAULT_TRANSPARENT_COLOR RGB(255,0,255)
//*****************************************************************************************************
//The struct data
//The image displayed information
#ifndef _DISPIMAGEINFO
#define _DISPIMAGEINFO
typedef struct
{
LONG imgID;
LONG left;
LONG top;
LONG right;
LONG bottom;
}DISPIMAGEINFO,*PDISPIMAGEINFO;
#endif //_DISPIMAGEINFO
//------------------------------------------------------------------------------
//The resource information
const DISPIMAGEINFO IMG_EXIT_ENABLE = {IDB_EXIT, 1,1,46,46};
const DISPIMAGEINFO IMG_EXIT_DISABLE = {IDB_EXIT, 93,1,138,46};
const DISPIMAGEINFO IMG_EXIT_PUSH = {IDB_EXIT, 47,1,92,46};
//*****************************************************************************************************
//-------------------------------------------------------------------
//Macro
#define IDC_BTN_EXIT 103
//-------------------------------------------------------------------
//const value
const RECT POS_EXIT = {0,0,45,45};
//-------------------------------------------------------------------
CMainWnd::CMainWnd(void):
m_hBnExit(NULL),
m_hdcBufBk(NULL),
m_hBmpBufBk(NULL),
m_hOldSelBufBk(NULL),
m_PreProcBnExit(NULL),
m_pszBnExit(NULL)
{
}
CMainWnd::~CMainWnd(void)
{
if(m_hdcBufBk != NULL && m_hOldSelBufBk != NULL)
{
SelectObject(m_hdcBufBk,m_hOldSelBufBk);
}
if(m_hBmpBufBk != NULL)
{
DeleteObject(m_hBmpBufBk);
m_hBmpBufBk = NULL;
}
if(m_hdcBufBk != NULL)
{
DeleteDC(m_hdcBufBk);
m_hdcBufBk = NULL;
}
if(m_pszBnExit != NULL)
{
delete []m_pszBnExit;
m_pszBnExit = NULL;
}
}
//----------------------------------------------------------------------
//Description:
// Create the window. It's override function
//
//----------------------------------------------------------------------
BOOL CMainWnd::Create(HINSTANCE hInst, HWND hWndParent, const TCHAR *pcszWndClass, const TCHAR *pcszWndName)
{
CWndBase::Create(hInst,hWndParent,pcszWndClass,pcszWndName);
//The button for exiting
m_hBnExit = CreateWindowEx(WS_EX_TOPMOST,
TEXT("BUTTON"),
TEXT(""),
BS_PUSHBUTTON | BS_CENTER | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW ,
POS_EXIT.left,
POS_EXIT.top,
POS_EXIT.right - POS_EXIT.left,
POS_EXIT.bottom - POS_EXIT.top,
m_hWnd,
(HMENU)IDC_BTN_EXIT,
m_hInst,
NULL);
//Store the pointer in the window
SetWindowLong(m_hBnExit, GWL_USERDATA, (DWORD)this);
//Set the new window procedure
m_PreProcBnExit = (WNDPROC)SetWindowLong(m_hBnExit,GWL_WNDPROC,(DWORD)CtrlProc);
SetWindowText(m_hBnExit,TEXT("X"));
return TRUE;
}
//----------------------------------------------------------------------
//Description:
// Window process. It's override function
//
//----------------------------------------------------------------------
LRESULT CMainWnd::WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
switch(wMsg)
{
case WM_DRAWITEM:
if(((LPDRAWITEMSTRUCT)lParam)->CtlID == IDC_BTN_EXIT)
{
OnDrawItemBtnExit(hWnd,wMsg,wParam,lParam);
return 0;
}
case WM_COMMAND:
{
if(HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_BTN_EXIT)
{
OnClickedBtnExit(hWnd,wMsg,wParam,lParam);
}
}
case WM_PAINT:
{
OnPaint(hWnd,wMsg,wParam,lParam);
return 0;
}
}
return DefWindowProc(hWnd,wMsg,wParam,lParam);
}
//----------------------------------------------------------------------
//Description:
// Windows control process
//
//----------------------------------------------------------------------
LRESULT CMainWnd::CtrlProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
CMainWnd *pObject = (CMainWnd *)GetWindowLong(hWnd,GWL_USERDATA);
if(pObject != NULL)
{
if(hWnd == pObject->m_hBnExit)
{
switch(wMsg)
{
case WM_SETTEXT:
{
if(pObject->m_pszBnExit != NULL)
{
delete []pObject->m_pszBnExit;
pObject->m_pszBnExit = NULL;
}
pObject->m_pszBnExit = new TCHAR [_tcslen( reinterpret_cast<</span>LPCTSTR>(lParam)) + 1];
_tcscpy(pObject->m_pszBnExit,reinterpret_cast<</span>LPCTSTR>(lParam));
break;
}
}
return CallWindowProc(pObject->m_PreProcBnExit,hWnd,wMsg,wParam,lParam);
}
}
return DefWindowProc(hWnd,wMsg,wParam,lParam);
}
//-----------------------------------------------------------------------------------------
//Description:
// On the message WM_DRAWITEM and it is for the button exit
//
//----------------------------------------------------------------------------------------------
void CMainWnd::OnDrawItemBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
HANDLE hBmp = LoadImage(m_hInst,MAKEINTRESOURCE(IDB_EXIT),IMAGE_BITMAP,0,0,0);
if(hBmp == NULL)
{
return ;
}
//Draw the background
BitBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
m_hdcBufBk,
POS_EXIT.left,
POS_EXIT.top,
SRCCOPY);
//Draw the button
HDC hdcBmp = CreateCompatibleDC(lpdis->hDC);
HGDIOBJ hOldSel = SelectObject(hdcBmp,hBmp);
if((lpdis->itemState & ODS_SELECTED ) && !(lpdis->itemState & ODS_DISABLED))
{
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_PUSH.left,
IMG_EXIT_PUSH.top,
IMG_EXIT_PUSH.right - IMG_EXIT_PUSH.left,
IMG_EXIT_PUSH.bottom - IMG_EXIT_PUSH.top,
DEFAULT_TRANSPARENT_COLOR);
}
else if(lpdis->itemState & ODS_DISABLED)
{
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_DISABLE.left,
IMG_EXIT_DISABLE.top,
IMG_EXIT_DISABLE.right - IMG_EXIT_DISABLE.left,
IMG_EXIT_DISABLE.bottom - IMG_EXIT_DISABLE.top,
DEFAULT_TRANSPARENT_COLOR);
}
else
{
TransparentBlt(lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
lpdis->rcItem.right - lpdis->rcItem.left,
lpdis->rcItem.bottom - lpdis->rcItem.top,
hdcBmp,
IMG_EXIT_ENABLE.left,
IMG_EXIT_ENABLE.top,
IMG_EXIT_ENABLE.right - IMG_EXIT_ENABLE.left,
IMG_EXIT_ENABLE.bottom - IMG_EXIT_ENABLE.top,
DEFAULT_TRANSPARENT_COLOR);
}
if(hdcBmp != NULL && hOldSel != NULL)
{
SelectObject(hdcBmp,hOldSel);
DeleteDC(hdcBmp);
hdcBmp = NULL;
hOldSel = NULL;
}
if(hBmp != NULL)
{
DeleteObject(hBmp);
hBmp = NULL;
}
//Draw the text
SetBkMode(lpdis->hDC,TRANSPARENT);
DrawText(lpdis->hDC,m_pszBnExit,-1,&lpdis->rcItem,DT_CENTER | DT_VCENTER);
}
//-----------------------------------------------------------------------------------------
//Description:
// On the message WM_COMMAND and the HIWORD(wParam) is BN_CLICKED for the button exit
//
//----------------------------------------------------------------------------------------------
void CMainWnd::OnClickedBtnExit(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
DestroyWindow(m_hWnd);
PostQuitMessage(0x00);
}
//-----------------------------------------------------------------------------------------
//Description:
// On the message WM_PAINT.
//
//----------------------------------------------------------------------------------------------
void CMainWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rcWnd = {0};
GetWindowRect(hWnd,&rcWnd);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd,&ps);
if(m_hdcBufBk == NULL)
{
//Create the memory DC for storing the background.
m_hBmpBufBk = CreateCompatibleBitmap(hdc,rcWnd.right - rcWnd.left,rcWnd.bottom - rcWnd.top);
m_hdcBufBk = CreateCompatibleDC(hdc);
m_hOldSelBufBk = SelectObject(m_hdcBufBk,m_hBmpBufBk);
//Draw the background
HPEN hPen=CreatePen(PS_SOLID,1,RGB(0,255,0));
HPEN hOldPen=NULL;
hOldPen=(HPEN)SelectObject(m_hdcBufBk,hPen);
//the rect color
HBRUSH hBrush = CreateSolidBrush(RGB(0,255,0));
HGDIOBJ hOldBrush = SelectObject(m_hdcBufBk,hBrush);
//Draw
Rectangle(m_hdcBufBk,0,0,rcWnd.right - rcWnd.left + 1, rcWnd.bottom - rcWnd.top + 1);
//Realse the resource
SelectObject(m_hdcBufBk,hOldBrush);
DeleteObject(hBrush);
SelectObject(m_hdcBufBk,hOldPen);
DeleteObject(hPen);
}
BitBlt(hdc,
rcWnd.left,
rcWnd.top,
rcWnd.right - rcWnd.left,
rcWnd.bottom - rcWnd.top,
m_hdcBufBk,
rcWnd.left,
rcWnd.top,
SRCCOPY);
EndPaint(hWnd,&ps);
UpdateWindow(m_hBnExit);
}