在开发软件界面时,往往美工设计的界面按钮都不会是标准按钮,因为标准按钮是固定的样式风格和对齐方式,如果用标准的按钮就会和软件的设计风格,颜色不搭. 所以在开发软件是界面时,自定义按钮往往是最常用到的功能. 可是如何实现?
按钮有最常见的响应按钮事件,有正常显示时的状态,鼠标移动上去时颜色会改变,鼠标点击时也有按下去的效果. 还有按钮上左边居中有特定的图标,或者按钮按下去之后保留按下去的状态(radio)等等. 如何做?
实现自定义按钮其实有两种方法. 绘制部分可以实现 WM_PAINT
事件,另外一种方式时创建按钮时加入BS_OWNERDRAW
样式. 这部分先介绍自定义实现WM_PAINT
的方式, 个人比较推荐这种.
选择使用消息WM_PAINT
来绘制按钮时, 按钮的有一个通知类型比较适用在鼠标移动到按钮区域和离开按钮区域时,BCN_HOTITEMCHANGE
, 通知按钮控件的拥有者鼠标进入或离开按钮的客户区域时. 按钮控件会以WM_NOTIFY
的方式发送这个通知编码. lParam
是指向NMBCHOTITEM
的一个指针.
pnmbchotitem = (NMBCHOTITEM*) lParam;
WTL
在自定义按钮消息映射表里声明的方式是使用REFLECTED_NOTIFY_CODE_HANDLER
, 因为在父控件里需要进行消息转发REFLECT_NOTIFICATIONS
,子控件比如自定义按钮创建时父控件才会把子控件的消息转发给子控件, 在父控件声明的REFLECT_NOTIFICATIONS
会把WM_COMMAND
,WM_DRAWITEM
,WM_NOTIFY
等消息类型转换为OCM_COMMAND
,OCM_NOTIFY
,OCM_DRAWITEM
等标识BCN_HOTITEMCHANGE
就是WM_NOTIFY
,所以自然需要#define REFLECTED_NOTIFY_CODE_HANDLER(cd, func) \
if(uMsg == OCM_NOTIFY && cd == ((LPNMHDR)lParam)->code) \
{ \
bHandled = TRUE; \
lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
if(bHandled) \
return TRUE; \
}
class BASSwitchButton:public CWindowImpl<BASSwitchButton,CButton>
{
public:
BEGIN_MSG_MAP_EX(BASSwitchButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
REFLECTED_NOTIFY_CODE_HANDLER(BCN_HOTITEMCHANGE,OnHotItemChange2)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
...
}
我们看看自定义按钮的父窗口消息映射声明
BEGIN_MSG_MAP_EX(CWTLSchoolView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_CREATE(OnCreate)
MESSAGE_HANDLER(WM_LBUTTONDOWN,OnLButtonDown)
COMMAND_RANGE_HANDLER_EX(MyWindowMinId,MyWindowNextButtonId,OnCommand)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
status_
的状态属性, 用以区分按钮所在的状态. OnPaint
并根据status_
来绘制不同状态下的颜色和形状. 而刚好BCN_HOTITEMCHANGE
通知类型可以识别进入和离开的状态, 分别对应kButtonStatusHover
和kButtonStatusNormal
, 按钮离开时, 我把按钮的颜色还原为正常状态.
#pragma once
#include
#include
#include
#include
#include
#include "atlframe.h"
#include
#include
#include
#include
#include
class BASSwitchButton:public CWindowImpl<BASSwitchButton,CButton>
{
public:
BEGIN_MSG_MAP_EX(BASSwitchButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
REFLECTED_NOTIFY_CODE_HANDLER(BCN_HOTITEMCHANGE,OnHotItemChange2)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
BASSwitchButton();
~BASSwitchButton();
void SetInitVisible(bool visible = true);
HWND CreateButton(HWND hWndParent,_U_MENUorID MenuOrID = 0U);
inline BASSwitchButton& SetRadio(bool radio)
{
is_radio_button_ = radio;
return *this;
}
inline BASSwitchButton& SetHMargin(int hmargin)
{
hmargin_ = hmargin;
return *this;
}
inline BASSwitchButton& SetVMargin(int vmargin)
{
vmargin_ = vmargin;
return *this;
}
inline BASSwitchButton& SetColorDisable(DWORD disable_bkg_color,DWORD disable_font_color)
{
disable_bkg_color_ = disable_bkg_color;
disable_font_color_ = disable_font_color;
return *this;
}
inline BASSwitchButton& SetBackgroundColor(DWORD color)
{
color_bg_ = color;
return *this;
}
inline BASSwitchButton& SetRoundRectPoint(CPoint point)
{
round_rect_point_ = point;
return *this;
}
inline BASSwitchButton& SetRoundRect(bool is_round_rect)
{
is_round_rect_ = is_round_rect;
return *this;
}
inline BASSwitchButton& 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 OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnHotItemChange2(int idCtrl, LPNMHDR pnmh, 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_switch_button.h"
#include
#include
#include
#include "utils.h"
namespace
{
enum
{
kButtonStatusNormal,
kButtonStatusPressed,
kButtonStatusHover,
kButtonStatusDisable
};
}
void BASSwitchButton::SetInitVisible(bool visible)
{
visible_ = visible;
}
BOOL BASSwitchButton::EnableWindow(BOOL bEnable)
{
status_ = (bEnable)?kButtonStatusNormal:kButtonStatusDisable;
return CWindowImpl::EnableWindow(bEnable);
}
BASSwitchButton::BASSwitchButton()
{
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 BASSwitchButton::SetFont(Gdiplus::Font* font,bool deleteOld)
{
if(deleteOld)
delete font_;
font_ = font;
}
BASSwitchButton::~BASSwitchButton()
{
Detach();
}
void BASSwitchButton::SetPressedStatus()
{
status_ = kButtonStatusPressed;
Invalidate(FALSE);
}
void BASSwitchButton::ResetStatus()
{
status_ = kButtonStatusNormal;
Invalidate(FALSE);
}
void BASSwitchButton::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);
}
LRESULT BASSwitchButton::OnHotItemChange2(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
//OutputDebugString(L"OnHotItemChange \n");
NMBCHOTITEM* item = (NMBCHOTITEM*)pnmh;
if(item->dwFlags & HICF_ENTERING){
//OutputDebugString(L"HICF_ENTERING \n");
if (!is_radio_button_){
status_ = kButtonStatusHover;
}else if(status_ != kButtonStatusPressed){
status_ = kButtonStatusHover;
}
}else{
status_ = kButtonStatusNormal;
//OutputDebugString(L"HICF_LEAVING \n");
}
SetMsgHandled(FALSE);
return 0;
}
void BASSwitchButton::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 BASSwitchButton::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// Ellipse
CPaintDC hdc(m_hWnd);
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);
bHandled = TRUE;
return 0;
}
HWND BASSwitchButton::CreateButton(HWND hWndParent,_U_MENUorID MenuOrID)
{
m_bTracking_ = false;
status_ = kButtonStatusNormal;
// BS_NOTIFY WS_CLIPCHILDREN
int flag = WS_CHILD | WS_CLIPCHILDREN ;
if(visible_)
flag |= WS_VISIBLE;
return Create(hWndParent,NULL,L"",flag,0,MenuOrID);
}
BOOL BASSwitchButton::OnEraseBkgnd(CDCHandle dc)
{
return TRUE;
}
LRESULT BASSwitchButton::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 BASSwitchButton::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 BASSwitchButton();
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));
BCN_HOTITEMCHANGE notification code
NM_CUSTOMDRAW
Button Styles
电子书 Programming Windows by Charles Petzold