WM_PAINT
消息来达到绘制按钮的目的, 并通过BCN_HOTITEMCHANGE
通知来处理鼠标进入和离开状态. 按钮控件有没有其他方式来绘制呢?不需要通过WM_PAINT
事件, 并且在绘制前就能知道按钮当前的状态?BS_OWNERDRAW
时, 按钮会接收到WM_DRAWITEM
事件. 这时候我们可以通过处理WM_DRAWITEM
事件来绘制按钮. 这个事件的lParam
参数是指向DRAWITEMSTRUCT
的指针. 这个结构体按道理应该能通过itemState
属性来判断进入和离开的事件, 可是itemState
的值在鼠标移动上去时并没有发送WM_DRAWITEM
消息. 所以如果要处理鼠标进入和离开的消息, 必须另外添加消息WM_MOUSEMOVE
,WM_MOUSEHOVER
,WM_MOUSELEAVE
处理.void BASCOwnerDrawButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
WM_MOUSEHOVER
和WM_MOUSELEAVE
默认是不发送的.只有当执行_TrackMouseEvent
函数时才会发送. 所以需要在WM_MOUSEMOVE
消息处理函数里添加鼠标跟踪, 并增加一个成员变量来作为跟踪启用标识m_bTracking_
, 初始化为false
.
if (!m_bTracking_)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE | TME_HOVER;//要触发的消息类型
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = 10;// 如果不设此参数,无法触发mouseHover
if (::_TrackMouseEvent(&tme)) //MOUSELEAVE|MOUSEHOVER消息由此函数触发.
{
m_bTracking_ = true;
}
}
return 0;
WM_MOUSEHOVER
和WM_MOUSELEAVE
理论进行状态status_
设置, 因为这两个消息看说明都是鼠标移上去或移出之后Posted
的消息,所以即使在消息处理函数里设置handled = FALSE
,按钮也不会刷新,所以需要自己调用Invalidate(FALSE)
来刷新按钮.
#pragma once
#include
#include
#include
#include
#include
#include "atlframe.h"
#include
#include
#include
#include
#include
class BASCOwnerDrawButton:public CWindowImpl<BASCOwnerDrawButton,CButton>,public COwnerDraw<BASCOwnerDrawButton>
{
public:
BEGIN_MSG_MAP_EX(BASCOwnerDrawButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove)
MESSAGE_HANDLER(WM_MOUSEHOVER,OnMouseHover)
MESSAGE_HANDLER(WM_MOUSELEAVE,OnMouseLeave)
CHAIN_MSG_MAP_ALT(COwnerDraw<BASCOwnerDrawButton>, 1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
BASCOwnerDrawButton();
~BASCOwnerDrawButton();
void SetInitVisible(bool visible = true);
HWND CreateButton(HWND hWndParent,_U_MENUorID MenuOrID = 0U);
inline BASCOwnerDrawButton& SetRadio(bool radio)
{
is_radio_button_ = radio;
return *this;
}
inline BASCOwnerDrawButton& SetHMargin(int hmargin)
{
hmargin_ = hmargin;
return *this;
}
inline BASCOwnerDrawButton& SetVMargin(int vmargin)
{
vmargin_ = vmargin;
return *this;
}
inline BASCOwnerDrawButton& SetColorDisable(DWORD disable_bkg_color,DWORD disable_font_color)
{
disable_bkg_color_ = disable_bkg_color;
disable_font_color_ = disable_font_color;
return *this;
}
inline BASCOwnerDrawButton& SetBackgroundColor(DWORD color)
{
color_bg_ = color;
return *this;
}
inline BASCOwnerDrawButton& SetRoundRectPoint(CPoint point)
{
round_rect_point_ = point;
return *this;
}
inline BASCOwnerDrawButton& SetRoundRect(bool is_round_rect)
{
is_round_rect_ = is_round_rect;
return *this;
}
inline BASCOwnerDrawButton& SetColorBorder(DWORD color_border)
{
color_border_ = color_border;
return *this;
}
BOOL Init(int nPosx,int nPosy,LPCWSTR text,
COLORREF normal_bkg_color,COLORREF normal_font_color,
COLORREF pressed_bkg_color,COLORREF pressed_font_color);
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void DrawRect(Gdiplus::Graphics& graphics,Gdiplus::Rect rect,DWORD color);
void ResetStatus();
void SetPressedStatus();
BOOL EnableWindow(_In_ BOOL bEnable = TRUE);
void SetFont(Gdiplus::Font* font,bool deleteOld = true);
protected:
LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnMouseHover(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
BOOL OnEraseBkgnd(CDCHandle dc);
private:
inline void SetTextColor(COLORREF color){
color_text_ = color;
}
void SetTextString(LPCWSTR text);
int nPosx_;
int nPosy_;
bool is_radio_button_;
Gdiplus::Font *font_;
COLORREF color_text_;
std::wstring text_;
DWORD normal_bkg_color_;
DWORD normal_font_color_;
DWORD pressed_bkg_color_;
DWORD pressed_font_color_;
DWORD disable_bkg_color_;
DWORD disable_font_color_;
int hmargin_;
int vmargin_;
int button_width_;
int button_height_;
int status_;
bool m_bTracking_;
bool pressed_;
bool is_round_rect_;
CPoint round_rect_point_;
COLORREF color_border_;
COLORREF color_bg_;
int scaling_; // 缩放系数
bool visible_; // 是否创建可见窗口.
};
#include "stdafx.h"
#include "bas_cownerdraw_button.h"
#include
#include
#include
#include "utils.h"
namespace
{
enum
{
kButtonStatusNormal,
kButtonStatusPressed,
kButtonStatusHover,
kButtonStatusDisable
};
}
void BASCOwnerDrawButton::SetInitVisible(bool visible)
{
visible_ = visible;
}
BOOL BASCOwnerDrawButton::EnableWindow(BOOL bEnable)
{
status_ = (bEnable)?kButtonStatusNormal:kButtonStatusDisable;
return CWindowImpl::EnableWindow(bEnable);
}
BASCOwnerDrawButton::BASCOwnerDrawButton()
{
disable_bkg_color_ = -1;
disable_font_color_ = -1;
color_bg_ = RGB(255,255,255);
visible_= true;
hmargin_ = 10;
vmargin_ = 10;
is_radio_button_ = true;
color_border_ = -1;
disable_bkg_color_ = RGB(127,127,127);
disable_font_color_ = RGB(255,255,255);
is_round_rect_ = false;
round_rect_point_.x = 6.0;
round_rect_point_.y = 6.0;
font_ = NULL;
}
void BASCOwnerDrawButton::SetFont(Gdiplus::Font* font,bool deleteOld)
{
if(deleteOld)
delete font_;
font_ = font;
}
BASCOwnerDrawButton::~BASCOwnerDrawButton()
{
Detach();
}
void BASCOwnerDrawButton::SetPressedStatus()
{
status_ = kButtonStatusPressed;
Invalidate(FALSE);
}
void BASCOwnerDrawButton::ResetStatus()
{
status_ = kButtonStatusNormal;
Invalidate(FALSE);
}
void BASCOwnerDrawButton::DrawRect(Gdiplus::Graphics& graphics,Gdiplus::Rect rect,DWORD color)
{
Gdiplus::GraphicsPath m_pPath;
m_pPath.AddRectangle(rect);
m_pPath.CloseFigure();
Gdiplus::Color color_bg(GetRValue(color_bg_),GetGValue(color_bg_),
GetBValue(color_bg_));
Gdiplus::SolidBrush brush_bg(color_bg);
graphics.FillPath(&brush_bg,&m_pPath);
Gdiplus::Pen *pen = NULL;
Gdiplus::Color gcolor(GetRValue(color),GetGValue(color),GetBValue(color));
if(color_border_ == -1){
pen = new Gdiplus::Pen(gcolor,1);
}else{
Gdiplus::Color color_border(GetRValue(color_border_),GetGValue(color_border_),
GetBValue(color_border_));
pen = new Gdiplus::Pen(color_border,1);
}
Gdiplus::SolidBrush brush_color(gcolor);
Gdiplus::GraphicsPath path_border;
if(is_round_rect_){
//Utils::CreateRoundRect(path_border,rect,round_rect_point_.x);
}else{
path_border.AddRectangle(rect);
path_border.CloseFigure();
}
graphics.FillPath(&brush_color,&path_border);
if(color_border_ != -1)
graphics.DrawPath(pen,&path_border);
}
void BASCOwnerDrawButton::SetTextString(LPCWSTR text)
{
text_ = text;
Gdiplus::Graphics graphics(m_hWnd);
Gdiplus::RectF rect_text;
graphics.MeasureString(text_.c_str(),text_.size(),font_,
Gdiplus::PointF(0,0),&rect_text);
button_width_ = rect_text.Width+hmargin_*2;
button_height_ = rect_text.Height + vmargin_*2;
}
LRESULT BASCOwnerDrawButton::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (!m_bTracking_)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE | TME_HOVER;//要触发的消息类型
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = 10;// 如果不设此参数,无法触发mouseHover
if (::_TrackMouseEvent(&tme)) //MOUSELEAVE|MOUSEHOVER消息由此函数触发.
{
m_bTracking_ = true;
}
}
return 0;
}
LRESULT BASCOwnerDrawButton::OnMouseHover(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (!is_radio_button_){
status_ = kButtonStatusHover;
}else if(status_ != kButtonStatusPressed){
status_ = kButtonStatusHover;
}
Invalidate(FALSE);
bHandled = FALSE;
return 0;
}
LRESULT BASCOwnerDrawButton::OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
status_ = kButtonStatusNormal;
Invalidate(FALSE);
m_bTracking_ = false;
bHandled = FALSE;
return TRUE;
}
void BASCOwnerDrawButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// Ellipse
HDC hdc = lpDrawItemStruct->hDC;
CRect rect;
GetClientRect(&rect);
Gdiplus::Bitmap bmp(int(rect.Width()),int(rect.Height()));
Gdiplus::Graphics graphics(&bmp);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
// 按钮窗口创建可能有1像素的边框区域,需要夸扩大左上区域1像素才可以把边界绘制到.
// 不清楚原因.
rect.InflateRect(1,1,0,0);
Gdiplus::Rect client_rect(rect.left,rect.top,rect.Width(),rect.Height());
switch(status_)
{
case kButtonStatusNormal:
{
SetTextColor(normal_font_color_);
DrawRect(graphics,client_rect,normal_bkg_color_);
break;
}
case kButtonStatusPressed:
{
SetTextColor(pressed_font_color_);
DrawRect(graphics,client_rect,pressed_bkg_color_);
break;
}
case kButtonStatusHover:
{
SetTextColor(pressed_font_color_);
DrawRect(graphics,client_rect,pressed_bkg_color_);
break;
}
case kButtonStatusDisable:
{
SetTextColor(disable_font_color_);
DrawRect(graphics,client_rect,disable_bkg_color_);
break;
}
}
if(text_.size()){
Gdiplus::RectF rect(client_rect.X,client_rect.Y,client_rect.Width,client_rect.Height);
Gdiplus::Color color_text;
color_text.SetFromCOLORREF(color_text_);
Utils::DrawTextCenter(graphics,rect,text_.c_str(),font_,&color_text);
}
///*Drawing on DC*/
Gdiplus::Graphics graphics1(hdc);
///*Important! Create a CacheBitmap object for quick drawing*/
Gdiplus::CachedBitmap cachedBmp(&bmp,&graphics1);
graphics1.DrawCachedBitmap(&cachedBmp,0,0);
SetMsgHandled(FALSE);
}
HWND BASCOwnerDrawButton::CreateButton(HWND hWndParent,_U_MENUorID MenuOrID)
{
m_bTracking_ = false;
status_ = kButtonStatusNormal;
// BS_NOTIFY WS_CLIPCHILDREN
int flag = WS_CHILD | WS_CLIPCHILDREN |BS_OWNERDRAW;
if(visible_)
flag |= WS_VISIBLE;
return Create(hWndParent,NULL,L"",flag,0,MenuOrID);
}
BOOL BASCOwnerDrawButton::OnEraseBkgnd(CDCHandle dc)
{
return TRUE;
}
LRESULT BASCOwnerDrawButton::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
Gdiplus::FontFamily fontFamily(L"Arial");
font_ = new Gdiplus::Font(&fontFamily,16,
(false)?Gdiplus::FontStyleBold:Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);
color_text_ = RGB(0,0,0);
bHandled = FALSE;
return 0;
}
BOOL BASCOwnerDrawButton::Init(int nPosx,int nPosy,LPCWSTR text,
COLORREF normal_bkg_color,COLORREF normal_font_color,
COLORREF pressed_bkg_color,COLORREF pressed_font_color)
{
normal_bkg_color_ = normal_bkg_color;
normal_font_color_ = normal_font_color;
pressed_bkg_color_ = pressed_bkg_color;
pressed_font_color_ = pressed_font_color;
nPosx_ = nPosx;
nPosy_ = nPosy;
SetTextString(text);
HWND wndparent=::GetParent(m_hWnd);
RECT rcClient;
::GetClientRect(wndparent,&rcClient);
if (nPosx<0)
{
nPosx=rcClient.right+nPosx-button_width_;
}
if (nPosy<0)
{
nPosy=rcClient.bottom+nPosy-button_height_;
}
SetWindowPos(NULL,nPosx,nPosy,button_width_,button_height_,SWP_FRAMECHANGED);
return 0;
}
#ifndef UTILS_H
#define UTILS_H
#include
#include
#include
class Utils
{
public:
static std::wstring GetProductBinDir()
{
wchar_t szbuf[MAX_PATH];
::GetModuleFileNameW(NULL,szbuf,MAX_PATH);
::PathRemoveFileSpecW(szbuf);
int length = lstrlen(szbuf);
szbuf[length] = L'\\';
szbuf[length+1] = 0;
return szbuf;
}
static void DrawText(Gdiplus::Graphics& graphics,
Gdiplus::RectF rect,LPCWSTR text,Gdiplus::Font* font,
const Gdiplus::StringFormat* format,
Gdiplus::Color* color)
{
graphics.DrawString(text,wcslen(text),
font,rect,format,
&Gdiplus::SolidBrush(*color));
}
static void DrawTextCenter(Gdiplus::Graphics& graphics,
Gdiplus::RectF rect,LPCWSTR text,Gdiplus::Font* font,
Gdiplus::Color* color)
{
Gdiplus::StringFormat stringFormat;
stringFormat.SetAlignment(Gdiplus::StringAlignmentCenter);
stringFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter);
Utils::DrawText(graphics,rect,text,font,&stringFormat,color);
}
};
#endif
auto color_normal = RGB(13,164,230);
auto button = new BASCOwnerDrawButton();
button->SetInitVisible(true);
button->CreateButton(m_hWnd,(_id)?_id:++window_id_);
button->SetFont(font_16_normal_gdi_);
button->SetHMargin(30)
.SetVMargin(8)
.SetRadio(false)
.SetColorBorder(color_normal)
.SetRoundRect(false)
.SetBackgroundColor(RGB(255,255,255));
button->Init(0,0,title,color_normal,RGB(255,255,255),
RGB(234,245,249),RGB(0,0,0));
WM_DRAWITEM message
NM_CUSTOMDRAW
Button Styles
电子书 Programming Windows by Charles Petzold
TrackMouseEvent function
TRACKMOUSEEVENT
WM_MOUSEHOVER
WM_MOUSELEAVE message