常规的GDI自绘控件仅支持BMP图片,不支持png图片。png图片体积小,支持透明色,可以做圆角界面。自绘控件想要支持PNG图片,需要在MFC工程引入GDI+。需要注意的是MFC工程默认是不支持GDI+,需要手工引入相关的库与头文件,具体方法见我的博客相关的文章。
自绘按钮的关键是重载DrawItem()函数,在其中贴按键底图,绘制线框,绘画文字,切换按键的按下、焦点等状态。
第二个关键点是重载PreSubclassWindow()函数,在其中定义按键的风格,其中必须定义的风格是BS_OWNERDRAW,其它的风格酌情选用。
如果想实现控件叠加的效果,必须拦截WM_ERASEBKGND消息,因为CButton默认擦除背景的操作是复制对话框的背景色,而不是下面的控件的皮肤。
头文件:
#pragma once
#include "pch.h"
#include <atlimage.h>
//按钮类型
enum
{
BTN_TYPE_NORMAL = 0x10, //普通BTN
BTN_TYPE_CHECK, // CHECK BOX
BTN_TYPE_TWO_STATES // 2态按钮
};
class CPngButton : public CButton
{
public:
CPngButton();
virtual ~CPngButton();
public:
BOOL Init(UINT nBtnType, UINT resIDNormal, UINT resIDFocus, UINT resIDPress, UINT resIDDisenable,
UINT resIDSelect = 0, UINT resIDSFocus = 0, UINT resIDSPress = 0, UINT resIDSDisenable=0);
BOOL GetCheckStatus(); //BTN_TYPE_CHECK
void SetCheckStatus(BOOL isCheck); //BTN_TYPE_CHECK
void SetText(CString text);
void SetTextParam(Color& color, int fontSize=12, WCHAR* fontName = L"微软雅黑");
//内部实现
private:
Image* ImageFromResource(HINSTANCE hInstance, UINT uImgID, LPCTSTR lpType);
void ImageFree();
void PaintParent();
BOOL ShowImage(Graphics* graph, Image* pImage);
Image* TypeCalcNormal(LPDRAWITEMSTRUCT lpDIS);
Image* TypeCalcCheck(LPDRAWITEMSTRUCT lpDIS);
Image* TypeCalc2States(LPDRAWITEMSTRUCT lpDIS);
BOOL ShowText(Graphics* graph, WCHAR* text);
void CalcTextPos();
Color m_textColor;
WCHAR m_text[256];
Font* m_font;
int m_fontSize;
float m_xPos;
float m_yPos;
BOOL m_isInit;
protected:
void CancelHover();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg LRESULT OnMouseHOver(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
protected:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
virtual void PreSubclassWindow();
public:
bool m_bTracked;
private:
Image* m_imgNormal;
Image* m_imgFocus;
Image* m_imgPress;
Image* m_imgSelect;
Image* m_imgSFocus;
Image* m_imgDisenable;
Image* m_imgSDisenable;
Image* m_imgSPress;
UINT m_nBtnType;
BOOL m_bMenuOn; //BTN类型为BTN_TYPE_MENU时,是否处于按下的状态
BOOL m_bIsChecked;
BOOL m_bMouseOnButton;
// 重叠按钮
private:
#define CHILD_MAX 30
CWnd* m_child[CHILD_MAX];
int m_childIndex;
void PaintChild();
public:
void AddChild(CWnd* child);
};
.cpp文件
#include "pch.h"
#include "CPngButton.h"
CPngButton::CPngButton()
{
m_isInit = FALSE;
m_bTracked = false;
m_bMenuOn = FALSE;
m_imgNormal = NULL;
m_imgFocus = NULL;
m_imgPress = NULL;
m_imgDisenable = NULL;
m_imgSelect = NULL;
m_imgSFocus = NULL;
m_imgSPress = NULL;
m_imgSDisenable = NULL;
m_bIsChecked = FALSE;
m_bMouseOnButton = FALSE;
m_nBtnType = BTN_TYPE_NORMAL;
m_childIndex = 0;
for (int i = 0; i < CHILD_MAX; i++) {
m_child[i] = NULL;
}
m_textColor = Color(255, 10, 10, 10);
m_fontSize = 12;
m_font = new Font(L"宋体", m_fontSize);
m_text[0] = '\0';
m_xPos = 0.0;
m_yPos = 0.0;
}
void CPngButton::ImageFree()
{
if (m_imgNormal)
{
delete m_imgNormal;
m_imgNormal = NULL;
}
if (m_imgFocus)
{
delete m_imgFocus;
m_imgFocus = NULL;
}
if (m_imgPress)
{
delete m_imgPress;
m_imgPress = NULL;
}
if (m_imgSelect)
{
delete m_imgSelect;
m_imgSelect = NULL;
}
if (m_imgSFocus)
{
delete m_imgSFocus;
m_imgSFocus = NULL;
}
if (m_imgSPress)
{
delete m_imgSPress;
m_imgSPress = NULL;
}
if (m_imgDisenable)
{
delete m_imgDisenable;
m_imgDisenable = NULL;
}
if (m_imgSDisenable)
{
delete m_imgSDisenable;
m_imgSDisenable = NULL;
}
}
CPngButton::~CPngButton()
{
ImageFree();
if (m_font) {
delete m_font;
m_font = NULL;
}
}
BOOL CPngButton::Init(UINT nBtnType, UINT resIDNormal, UINT resIDFocus, UINT resIDPress, UINT resIDDisenable,
UINT resIDSelect, UINT resIDSFocus, UINT resIDSPress, UINT resIDSDisenable)
{
ImageFree();
HINSTANCE hInstance = AfxGetResourceHandle();
m_imgNormal = ImageFromResource(hInstance, resIDNormal, "PNG");
m_imgFocus = ImageFromResource(hInstance, resIDFocus, "PNG");
m_imgPress = ImageFromResource(hInstance, resIDPress, "PNG");
m_imgDisenable = ImageFromResource(hInstance, resIDDisenable, "PNG");
if (m_imgNormal == NULL || m_imgFocus == NULL || m_imgPress == NULL || m_imgDisenable == NULL) {
TRACE("ImageFromResource fail[%d]\n",GetLastError());
ImageFree();
return FALSE;
}
if (nBtnType == BTN_TYPE_CHECK) {
m_imgSelect = ImageFromResource(hInstance, resIDSelect, "PNG");
m_imgSFocus = ImageFromResource(hInstance, resIDSFocus, "PNG");
m_imgSPress = ImageFromResource(hInstance, resIDSPress, "PNG");
m_imgSDisenable = ImageFromResource(hInstance, resIDSDisenable, "PNG");
if (m_imgSelect == NULL || m_imgSFocus == NULL || m_imgSPress == NULL || m_imgSDisenable == NULL) {
TRACE("ImageFromResource fail[%d]\n", GetLastError());
ImageFree();
return FALSE;
}
}
m_nBtnType = nBtnType;
SetWindowPos(NULL, 0, 0, m_imgNormal->GetWidth(), m_imgNormal->GetHeight(), SWP_NOACTIVATE | SWP_NOMOVE);
CalcTextPos();
m_isInit = TRUE;
return TRUE;
}
void CPngButton::SetText(CString text)
{
MultiByteToWideChar(CP_ACP, 0, text.GetBuffer(), -1, m_text, 255);
text.ReleaseBuffer();
CalcTextPos();
if (m_isInit) {
Invalidate();
}
}
void CPngButton::CalcTextPos()
{
if (wcslen(m_text) > 0) {
CRect rect;
GetClientRect(&rect);
m_xPos = rect.Width() / 2;
m_yPos = (rect.Height() - m_fontSize * 2) / 2;
m_yPos = m_yPos < 0 ? 0 : m_yPos;
}
}
void CPngButton::SetTextParam(Color& color, int fontSize, WCHAR* fontName)
{
m_textColor = color;
if (m_font) {
delete m_font;
m_font = NULL;
}
m_font = new Font(fontName, fontSize);
m_fontSize = fontSize;
CalcTextPos();
}
BEGIN_MESSAGE_MAP(CPngButton, CButton)
//{{AFX_MSG_MAP(CPngButton)
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_PAINT()
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHOver)
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
// CPngButton message handlers
void CPngButton::OnPaint()
{
CButton::OnPaint();
}
Image* CPngButton::TypeCalcNormal(LPDRAWITEMSTRUCT lpDIS)
{
BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
BOOL isFocused = (lpDIS->itemState & ODS_FOCUS); //是不是获得了焦点
BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED); //是不是灰化
BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能
/*if (isPressed) {
m_bIsChecked = !m_bIsChecked;
}*/
Image* image = NULL;
if (isGrayed || isDisabled) {
image = m_imgDisenable;
}
else if (isPressed) {
image = m_imgPress;
}else {
if (m_bMouseOnButton) {
image = m_imgFocus;
}else {
image = m_imgNormal;
}
}
return image;
}
Image* CPngButton::TypeCalcCheck(LPDRAWITEMSTRUCT lpDIS)
{
static BOOL oldPress = -1;
BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
BOOL isFocused = (lpDIS->itemState & ODS_FOCUS); //是不是获得了焦点
BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED); //是不是灰化
BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能
//TRACE("TypeCalcCheck() P=%d F=%d gray=%d dis=%d\n", isPressed, isFocused, isGrayed, isDisabled);
if (oldPress != isPressed) {
oldPress = isPressed;
if (isPressed) {
m_bIsChecked = !m_bIsChecked;
}
}
Image* image = NULL;
if (m_bIsChecked) {
if (isGrayed || isDisabled) {
image = m_imgSDisenable;
}
else if (isPressed) {
image = m_imgSPress;
}
else {
if (m_bMouseOnButton) {
image = m_imgSFocus;
}
else {
image = m_imgSelect;
}
}
}else {
if (isGrayed || isDisabled) {
image = m_imgDisenable;
}
else if (isPressed) {
image = m_imgPress;
}
else {
if (m_bMouseOnButton) {
image = m_imgFocus;
}
else {
image = m_imgNormal;
}
}
}
return image;
}
Image* CPngButton::TypeCalc2States(LPDRAWITEMSTRUCT lpDIS)
{
BOOL isPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已经按下
BOOL isFocused = (lpDIS->itemState & ODS_FOCUS); //是不是获得了焦点
BOOL isGrayed = (lpDIS->itemState & ODS_GRAYED); //是不是灰化
BOOL isDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是没使能
/*if (isPressed) {
m_bIsChecked = !m_bIsChecked;
}*/
Image* image = NULL;
if (isGrayed || isDisabled) {
image = m_imgDisenable;
}else if (m_bIsChecked) {
image = m_imgFocus;
}else {
image = m_imgNormal;
}
return image;
}
void CPngButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
//TRACE("CPngButton::DrawItem(%p)\n",m_imgNormal);
CDC dc;
dc.Attach(lpDIS->hDC);
//dc.SetBkMode(TRANSPARENT);
Image* image = NULL;
switch (m_nBtnType) {
case BTN_TYPE_NORMAL:
image = TypeCalcNormal(lpDIS);
break;
case BTN_TYPE_CHECK:
image = TypeCalcCheck(lpDIS);
break;
case BTN_TYPE_TWO_STATES:
image = TypeCalc2States(lpDIS);
break;
}
Graphics graph(dc.GetSafeHdc());
ShowImage(&graph, image);
if (wcslen(m_text) > 0) {
ShowText(&graph, m_text);
}
graph.ReleaseHDC(dc.GetSafeHdc());
PaintChild();
}
BOOL CPngButton::ShowText(Graphics* graph, WCHAR *text)
{
PointF origin(m_xPos, m_yPos);
SolidBrush brush(m_textColor);
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
graph->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
Status hr = graph->DrawString(text, wcslen(text), m_font, origin, &format, &brush);
//TRACE("graph.DrawString() hr=%d\n",hr);
if (hr != 0) {
return FALSE;
}
return TRUE;
}
BOOL CPngButton::ShowImage(Graphics* graph, Image* pImage)
{
bool bSuc = false;
if (pImage != NULL)
{
CRect rcButton;
rcButton = CRect(0, 0, m_imgNormal->GetWidth(), m_imgNormal->GetHeight());
//Graphics graph(pDC->GetSafeHdc());
graph->DrawImage(pImage, 0, 0, rcButton.left, rcButton.top, rcButton.Width(), rcButton.Height(), UnitPixel);
//graph.ReleaseHDC(pDC->GetSafeHdc());
bSuc = true;
}
return bSuc;
}
Image* CPngButton::ImageFromResource(HINSTANCE hInstance, UINT uImgID, LPCTSTR lpType)
{
HRSRC hResInfo = ::FindResource(hInstance, MAKEINTRESOURCE(uImgID), lpType);
if (hResInfo == NULL)
return NULL; //fail
DWORD dwSize;
dwSize = SizeofResource(hInstance, hResInfo); //get resource size(bytes)
HGLOBAL hResData;
hResData = ::LoadResource(hInstance, hResInfo);
if (hResData == NULL)
return NULL; //fail
HGLOBAL hMem;
hMem = ::GlobalAlloc(GMEM_MOVEABLE, dwSize);
if (hMem == NULL) {
::FreeResource(hResData);
return NULL;
}
LPVOID lpResData, lpMem;
lpResData = ::LockResource(hResData);
lpMem = ::GlobalLock(hMem);
::CopyMemory(lpMem, lpResData, dwSize); //copy memory
::GlobalUnlock(hMem);
::FreeResource(hResData); //free memory
IStream* pStream;
HRESULT hr;
hr = ::CreateStreamOnHGlobal(hMem, TRUE, &pStream);//create stream object
Image* pImage = NULL;
if (SUCCEEDED(hr)) {
pImage = Image::FromStream(pStream);//get GDI+ pointer
pStream->Release();
}
::GlobalFree(hMem);
return pImage;
}
void CPngButton::PreSubclassWindow()
{
ModifyStyle(0, BS_OWNERDRAW);
ModifyStyle(WS_CLIPCHILDREN, 0);
ModifyStyle(WS_CLIPSIBLINGS, 0);
CButton::PreSubclassWindow();
}
BOOL CPngButton::OnEraseBkgnd(CDC* pDC)
{
//TRACE("CPngButton::OnEraseBkgnd()\n");
return TRUE;
}
void CPngButton::CancelHover()
{
if (m_bMouseOnButton)
{
m_bMouseOnButton = FALSE;
Invalidate();
}
}
void CPngButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CWnd* wndUnderMouse = NULL;
CWnd* wndActive = this;
TRACKMOUSEEVENT csTME;
CButton::OnMouseMove(nFlags, point);
ClientToScreen(&point);
wndUnderMouse = WindowFromPoint(point);//获得点下面的组件
// If the mouse enter the button with the left button pressed then do nothing
if (nFlags & MK_LBUTTON && m_bMouseOnButton == FALSE)
return;
if (wndUnderMouse && wndUnderMouse->m_hWnd == m_hWnd && wndActive)
{
if (!m_bMouseOnButton)
{
m_bMouseOnButton = TRUE;
Invalidate();
csTME.cbSize = sizeof(csTME);
csTME.dwFlags = TME_LEAVE;
csTME.hwndTrack = m_hWnd;
::_TrackMouseEvent(&csTME);//发送鼠标离开的消息,见我之前的博客
}
}
else {
CancelHover();
}
CButton::OnMouseMove(nFlags, point);
}
void CPngButton::OnLButtonDown(UINT nFlags, CPoint point)
{
CButton::OnLButtonDown(nFlags, point);
}
void CPngButton::OnLButtonUp(UINT nFlags, CPoint point)
{
CButton::OnLButtonUp(nFlags, point);
}
LRESULT CPngButton::OnMouseHOver(WPARAM wParam, LPARAM lParam)
{
//鼠标放上去时
//TRACE("OnMouseHOver()\n");
return 0;
}
LRESULT CPngButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
//鼠标移开时
//TRACE("OnMouseLeave()\n");
CancelHover();
return 0;
}
void CPngButton::PaintParent()
{
CRect rect;
GetWindowRect(&rect);
GetParent()->ScreenToClient(&rect);
GetParent()->InvalidateRect(&rect);
}
void CPngButton::AddChild(CWnd* child)
{
if (m_childIndex < CHILD_MAX) {
m_child[m_childIndex++] = child;
}else {
TRACE("AddChild()error:full\n");
}
}
void CPngButton::PaintChild()
{
for (int i = 0; i < CHILD_MAX; i++) {
if (m_child[i]) {
m_child[i]->Invalidate();
}
}
}
BOOL CPngButton::GetCheckStatus() //BTN_TYPE_CHECK
{
return m_bIsChecked;
}
void CPngButton::SetCheckStatus(BOOL isCheck) //BTN_TYPE_CHECK
{
m_bIsChecked = isCheck;
if (m_isInit) {
Invalidate();
}
}