[WTL/ATL]_[中级]_[自定义按钮1]

场景

  1. 在开发软件界面时,往往美工设计的界面按钮都不会是标准按钮,因为标准按钮是固定的样式风格和对齐方式,如果用标准的按钮就会和软件的设计风格,颜色不搭. 所以在开发软件是界面时,自定义按钮往往是最常用到的功能. 可是如何实现?

  2. 按钮有最常见的响应按钮事件,有正常显示时的状态,鼠标移动上去时颜色会改变,鼠标点击时也有按下去的效果. 还有按钮上左边居中有特定的图标,或者按钮按下去之后保留按下去的状态(radio)等等. 如何做?

说明

  1. 实现自定义按钮其实有两种方法. 绘制部分可以实现 WM_PAINT事件,另外一种方式时创建按钮时加入BS_OWNERDRAW样式. 这部分先介绍自定义实现WM_PAINT的方式, 个人比较推荐这种.

  2. 选择使用消息WM_PAINT来绘制按钮时, 按钮的有一个通知类型比较适用在鼠标移动到按钮区域和离开按钮区域时,BCN_HOTITEMCHANGE, 通知按钮控件的拥有者鼠标进入或离开按钮的客户区域时. 按钮控件会以WM_NOTIFY的方式发送这个通知编码. lParam是指向NMBCHOTITEM的一个指针.

 pnmbchotitem = (NMBCHOTITEM*) lParam;
  1. 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; \
	}
  1. 以下是声明自定义按钮1的声明
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()
  1. 自定义按钮里声明了一个status_的状态属性, 用以区分按钮所在的状态. OnPaint并根据status_来绘制不同状态下的颜色和形状. 而刚好BCN_HOTITEMCHANGE通知类型可以识别进入和离开的状态, 分别对应kButtonStatusHoverkButtonStatusNormal, 按钮离开时, 我把按钮的颜色还原为正常状态.

代码

bas_switch_button.h: 这里有些按钮属性不是必要的, 可以自己删减.


#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_; // 是否创建可见窗口.
};

bas_switch_button.cpp


#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;
}

utils.h


#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));

图1: 正常状态
在这里插入图片描述
图2: 鼠标移上去状态
在这里插入图片描述

参考

BCN_HOTITEMCHANGE notification code

NM_CUSTOMDRAW

Button Styles

电子书 Programming Windows by Charles Petzold

你可能感兴趣的:(ATL/WTL界面开发)