本文参考了CButtonST代码实现。 CButtonST 的功能相当丰富,比较复杂,支持声音等等。本文对其进行了大量简化,完成了一种效果还算不错的flat风格按钮。
按钮风格设计
按钮的简单外界影响,无非是 鼠标离开,鼠标在按钮上和点击等单个状态(其他状态暂不考虑)。三个状态,一般的按钮设计要对应三种图标或者图片,本文利用图像处理的简单技巧,每个按钮只输入一个图片就可以了。其余两种状态的图片是系统计算出来的,这样做的好处是代码简洁,图片文件也只有原来的三分之一。对应界面设计的外行来说,这样省去很多麻烦。
笔者设计了一个CSingleFlatButton类,实现了一个简单的flatbutton。
效果如图
鼠标没有经过按钮是的按钮颜色,是蓝色,如图示。
鼠标移到按钮上方是,按钮颜色发生变化。这个变化是程序通过改变贴图实现的,而贴图是自动计算出来的。
鼠标点击时的效果,按钮图片是一个简单的正透视效果,实际效果有被推倒的感觉。
下面是代码
#include "stdafx.h"
#include "CSingleFlatButton.h"
CSingleFlatButton::CSingleFlatButton()
{
m_bMouseOnButton = false;
m_bDrawBorder = true;
m_bIsFlat = true;
m_bIsPressed = false;
}
CSingleFlatButton::~CSingleFlatButton()
{
}
BEGIN_MESSAGE_MAP(CSingleFlatButton, CButton)
ON_WM_MOUSEMOVE()
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()
void CSingleFlatButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect itemRect = lpDIS->rcItem;
m_bIsPressed = (lpDIS->itemState & ODS_SELECTED);
// Draw button border
OnDrawBorder(pDC, &itemRect);
int x, y;
if (m_bIsPressed) {
COLORREF clr = RGB(128, 128, 128);
//clr = CTLCOLOR_STATIC;
clr = pDC->GetBkColor();
CBrush *brush = new CBrush(clr);
//GetDlgItem(IDC_STATIC)->GetDC()->FillRect(rect, brush);
pDC->FillRect(itemRect, brush);
//imgCtrl->ReleaseDC(imgCtrl->GetDC());
delete brush;
CDC *cdc = GetDC();
m_pressImg.Draw(cdc->m_hDC,
itemRect.left,
itemRect.top,
itemRect.Width(),
itemRect.Height());
ReleaseDC(cdc);
//DrawState does not support alpha channel ...
/*pDC->DrawState(CPoint(x, y), CSize(m_mouseOnImg.GetWidth(), m_mouseOnImg.GetHeight()),
m_mouseOnImg, DST_BITMAP);*/
}else if (m_bMouseOnButton)
{
x = itemRect.left +1;
y = itemRect.top +1;
pDC->DrawState(CPoint(x, y), CSize(m_mouseOnImg.GetWidth(), m_mouseOnImg.GetHeight()),
m_mouseOnImg, DST_BITMAP );
}
else {
x = itemRect.left/*+1*/;
y = itemRect.top;
pDC->DrawState(CPoint(x,y), CSize(m_mouseLeaveImg.GetWidth(), m_mouseLeaveImg.GetHeight()),
m_mouseLeaveImg, DST_BITMAP );
}
}
BOOL CSingleFlatButton::PreTranslateMessage(MSG * pMsg)
{
return CButton::PreTranslateMessage(pMsg);
}
void CSingleFlatButton::setImg(CImage img)
{
//normalImg = img;
perspectiveImg(img, img.GetHeight(), img.GetWidth(), L"D", m_mouseLeaveImg);
createRelatedImg();
}
void CSingleFlatButton::setImg(CString imgFile)
{
if (!m_mouseLeaveImg.IsNull()) {
m_mouseLeaveImg.Destroy();
}
m_mouseLeaveImg.Load(imgFile);
createRelatedImg();
}
void CSingleFlatButton::PreSubclassWindow()
{
ModifyStyle(0, BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
void CSingleFlatButton::createRelatedImg()
{
int tH, tTopL;
tH = m_mouseLeaveImg.GetHeight()*0.8;
tTopL = m_mouseLeaveImg.GetWidth()*0.8;
perspectiveImg(m_mouseLeaveImg, tH, tTopL, L"D", m_pressImg);
m_myImg.FireStyle(m_mouseLeaveImg, m_mouseOnImg);
}
// the dst image is the as the same size as the original one.
void CSingleFlatButton::perspectiveImg(CImage &src, int tH, int tTopL, CString dir, CImage &dst) {
int src_width = src.GetWidth();
int src_height = src.GetHeight();
CImage img1;
float dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH;
int tBotL;
if (dir == "U" || dir == "D") {
dstH = tH;
dstW = src_width;
}
else {
dstH = src_height;
dstW = tH;
}
img1.Create(dstW, dstH, 24);
CImage dst0;
if (!dst0.IsNull()) {
dst0.Destroy();
}
dst0.Create(src.GetWidth(), src.GetHeight(), 24);
CDC* pSrc0DC = CDC::FromHandle(src.GetDC());
CDC* pSrc1DC = CDC::FromHandle(img1.GetDC());
CDC* pDstDC = CDC::FromHandle(dst0.GetDC());
int ModeOld = SetStretchBltMode(pSrc1DC->m_hDC, COLORONCOLOR);//设置指定设备环境中的位图拉伸模式, looks like STRETCH_HALFTONE mode, 0.16 ms or 0.00 ms
//1# get imag1
pSrc1DC->StretchBlt(0, 0, dstW, dstH, pSrc0DC, 0, 0, src_width, src_height, SRCCOPY);
SetStretchBltMode(pSrc1DC->m_hDC, ModeOld);
ModeOld = SetStretchBltMode(pDstDC->m_hDC, COLORONCOLOR);//设置指定设备环境中的位图拉伸模式, looks like STRETCH_HALFTONE mode, 0.16 ms or 0.00 ms
if (dir == "U" || dir == "D") {
srcH = 1;
dstH = 1;
srcX = 0;
srcW = src_width;
tBotL = src_width;
}
else {
srcW = 1;
dstW = 1;
srcY = 0;
srcH = src_height;
tBotL = src_height;
}
int dL;// the difference of the two parallel edge of the trapezoid
int L;
for (int h = 0; h < tH; h++)
{
if (dir == "U") {
srcY = h ;
dstY = srcY;
dL = (tBotL - tTopL) / 2.0 / tH * h;
L = tBotL - dL * 2;
dstW = L;
dstX = dL;
}
else if (dir == "D") {
srcY = tH - h;
dstY = srcY + src_height -tH;
dL = (tBotL - tTopL) / 2.0 / tH * h;
L = tBotL - dL * 2;
dstW = L;
dstX = dL;
}
else if (dir == "L") {
srcX = h;
dstX = srcX;
dL = (tBotL - tTopL) / 2.0 / tH * h;
L = tBotL - dL * 2;
dstH = L;
dstY = dL;
}
else if (dir == "R") {
srcX = tH - h;
dstX = srcX + src_width-tH;
dL = (tBotL - tTopL) / 2.0 / tH * h;
L = tBotL - dL * 2;
dstH = L;
dstY = dL;
}
else {
throw "Bad parameters...";
}
pDstDC->StretchBlt(dstX, dstY, dstW, dstH, pSrc1DC, srcX, srcY, srcW, srcH, SRCCOPY);
}
SetStretchBltMode(pDstDC->m_hDC, ModeOld);
src.ReleaseDC();
img1.ReleaseDC();
dst0.ReleaseDC();
GetTransparentIMG(RGB(255, 255, 255), 0, dst0, dst);
return;
}
// parameters:
// transClr: the color need to be transparent
// alpha: 0-255 the value for alphas channel , 0 is fully transparent and 255 is opaque
// srcImg: 16bit above image
// dstImg: the output image with alpha channel
void CSingleFlatButton::GetTransparentIMG(COLORREF transClr, int alpha, CImage srcImg, CImage & dstImg)
{
//imgSrc = (CImage)this;
int maxY = srcImg.GetHeight();
int maxX = srcImg.GetWidth();
if (!dstImg.IsNull())
{
dstImg.Destroy();
}
dstImg.Create(maxX, maxY, 32, CImage::createAlphaChannel);//图像大小与srcImg相同,每个像素占1字节
if (dstImg.IsNull())
return;
byte* pDataSrc = (byte*)srcImg.GetBits();//获取指向图像数据的指针
byte* pDataDst = (byte*)dstImg.GetBits();
int pitchSrc = srcImg.GetPitch(); //获取每行图像占用的字节数 +:top-down;-:bottom-up DIB
int pitchDst = dstImg.GetPitch();
int bitCountSrc = srcImg.GetBPP() / 8; // 获取每个像素占用的字节数
int bitCountDst = dstImg.GetBPP() / 8;
int srcR, srcG, srcB, avg;
for (int i = 0; i < maxX; i++)
{
for (int j = 0; j < maxY; j++)
{
srcB = *(pDataSrc + pitchSrc * j + i * bitCountSrc);
srcG = *(pDataSrc + pitchSrc * j + i * bitCountSrc + 1);
srcR = *(pDataSrc + pitchSrc * j + i * bitCountSrc + 2);
avg = (int)(srcR + srcG + srcB) / 3;
long bkColor = srcR * srcG*srcB;
if (transClr == RGB(srcR, srcG, srcB))
{
*(pDataDst + pitchDst * j + i * bitCountDst + 3) = alpha;// transparent
}
else {
*(pDataDst + pitchDst * j + i * bitCountDst + 3) = 255;// opaque
*(pDataDst + pitchDst * j + i * bitCountDst) = srcB;
*(pDataDst + pitchDst * j + i * bitCountDst + 1) = srcG;
*(pDataDst + pitchDst * j + i * bitCountDst + 2) = srcR;
}
}
}
return;
}
void CSingleFlatButton::OnMouseMove(UINT nFlags, CPoint point)
{
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 our button is not flat then do nothing
//if (m_bIsFlat == FALSE) return;
//if (m_bAlwaysTrack == FALSE) wndActive = GetActiveWindow();
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);
} // if
}
else CancelHover();
}
LRESULT CSingleFlatButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
CancelHover();
return 0;
} // End of OnMouseLeave
void CSingleFlatButton::CancelHover()
{
// Only for flat buttons
if (m_bIsFlat)
{
if (m_bMouseOnButton)
{
m_bMouseOnButton = FALSE;
Invalidate();
} // if
} // if
} // End
bool CSingleFlatButton::OnDrawBorder(CDC* pDC, CRect* pRect)
{
// Draw pressed button
if (m_bIsPressed)
{
if (m_bIsFlat)
{
if (m_bDrawBorder)
pDC->Draw3dRect(pRect, ::GetSysColor(COLOR_BTNSHADOW), ::GetSysColor(COLOR_BTNHILIGHT));
}
else
{
CBrush brBtnShadow(GetSysColor(COLOR_BTNSHADOW));
pDC->FrameRect(pRect, &brBtnShadow);
}
}
else // ...else draw non pressed button
{
CPen penBtnHiLight(PS_SOLID, 0, GetSysColor(COLOR_BTNHILIGHT)); // White
CPen pen3DLight(PS_SOLID, 0, GetSysColor(COLOR_3DLIGHT)); // Light gray
CPen penBtnShadow(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW)); // Dark gray
CPen pen3DDKShadow(PS_SOLID, 0, GetSysColor(COLOR_3DDKSHADOW)); // Black
if (m_bIsFlat)
{
if (m_bMouseOnButton && m_bDrawBorder)
pDC->Draw3dRect(pRect, ::GetSysColor(COLOR_BTNHILIGHT), ::GetSysColor(COLOR_BTNSHADOW));
}
else
{
// Draw top-left borders
// White line
CPen* pOldPen = pDC->SelectObject(&penBtnHiLight);
pDC->MoveTo(pRect->left, pRect->bottom - 1);
pDC->LineTo(pRect->left, pRect->top);
pDC->LineTo(pRect->right, pRect->top);
// Light gray line
pDC->SelectObject(pen3DLight);
pDC->MoveTo(pRect->left + 1, pRect->bottom - 1);
pDC->LineTo(pRect->left + 1, pRect->top + 1);
pDC->LineTo(pRect->right, pRect->top + 1);
// Draw bottom-right borders
// Black line
pDC->SelectObject(pen3DDKShadow);
pDC->MoveTo(pRect->left, pRect->bottom - 1);
pDC->LineTo(pRect->right - 1, pRect->bottom - 1);
pDC->LineTo(pRect->right - 1, pRect->top - 1);
// Dark gray line
pDC->SelectObject(penBtnShadow);
pDC->MoveTo(pRect->left + 1, pRect->bottom - 2);
pDC->LineTo(pRect->right - 2, pRect->bottom - 2);
pDC->LineTo(pRect->right - 2, pRect->top);
//
pDC->SelectObject(pOldPen);
} // else
} // else
//return BTNST_OK;
return true;
} // End of OnDrawBorder
下面是H文件
#pragma once
#include <afxwin.h>
#include "CMyImage.h"
class CSingleFlatButton :
public CButton
{
public:
CSingleFlatButton();
~CSingleFlatButton();
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
virtual BOOL PreTranslateMessage(MSG* pMsg);
void setImg(CImage img);
void setImg(CString imgFile);
protected:
virtual void PreSubclassWindow();
private:
CImage m_mouseLeaveImg;//need to be set by the user
CImage m_mouseOnImg;// generated from the normaImg
CImage m_pressImg;//generated from the normalImg
bool m_bMouseOnButton;
bool m_bIsFlat;
bool m_bDrawBorder;
bool m_bIsPressed;
CMyImage m_myImg;//used to process image
private:
void createRelatedImg();
void perspectiveImg(CImage &src, int tH, int tTopL, CString dir, CImage &dst);
void GetTransparentIMG(COLORREF transClr, int alpha,CImage srcImg, CImage & dstImg);
protected:
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
void CancelHover();
bool OnDrawBorder(CDC* pDC, CRect* pRect);
DECLARE_MESSAGE_MAP()
};
使用方法:
定义控件变量
CSingleFlatButton m_btnBrowse;
CSingleFlatButton m_btnPrev;
…
在OnInitDialog()中添加如下代码
// m_btnImgPath 存放图片所在文件夹
CString filePath;
filePath = m_btnImgPath + L"browse.png";
m_btnBrowse.setImg(filePath);
filePath = m_btnImgPath + L"Maximum.png";
m_btnMaximum.setImg(filePath);
说明:mousemove事件中增加了Mouseleav 事件生成的代码,涉及到TRACKMOUSEEVENT,读者可自行网搜之。
下一步将使用文件方式改造成使用资源的方式,这样代码封闭性好一些。
2020-04-10 于泛五道口地区