在MFC实现上面的安卓风格的复合按钮是有难度的,尝试了好几种方法,显示闪烁问题都是绕不过去的坎。幸得高手指点,完美解决了此问题,分享给大家。
头文件:
#pragma once
class CItemView : public CButton
{
DECLARE_DYNAMIC(CItemView)
CItemView();
virtual ~CItemView();
protected:
DECLARE_MESSAGE_MAP()
// ReSharper disable once IdentifierTypo
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseHover(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
void PreSubclassWindow() override;
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) override;
//接口
public:
void SetImages(UINT nNormalId, UINT nHoverId, UINT nPressId, UINT nDisableId = 0, LPCTSTR lpszResourceType = _T("PNG"));//button
void SetIcon(UINT nId);
void SetTextParam(COLORREF cr);
void SetCheckImage(UINT nCheckId, UINT nNormalId); // check box
void SetValueString(CString &cs,int rightIconID = -1, int redDotID = -1); // text
void SetValueText(CString& str);
void SetValueRedDot(BOOL on);
void SetSelectValue(CString strInit, int itemCount, UINT boxID); // select box
void SetSelectText(CString str);
int GetSelectStaus();
static void LoadImageFromResource(CImage& img, UINT nId, LPCTSTR lpszResourceType = "PNG");
virtual BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
private:
void outputText(CDC *cdc, int nDpi);
protected:
void ReleaseImages();
BOOL m_isInit;
enum State {
Normal = 0,
Hover,
Press,
Disable,
};
CImage m_images[4];
State m_state = Normal;
BOOL m_bMouseTrack;
CImage m_icon;
CImage m_checkImage[2];//0: normal, 1: check
CRect m_itemValueRect;
COLORREF m_textColor;
CString m_valueString;
CImage m_rightIcon; // 右图标
BOOL m_isRedDot; // 红点
CImage m_imgRedDot; // 红点图片
CString m_selectText;
UINT m_selectBoxID;
int m_selectorCount;
int m_selectStaus; // 0-无 1-鼠标点击
public:
bool m_isCheck;
};
.cpp文件
// ImageButton.cpp: 实现文件
//
#include "pch.h"
#include "ItemView.h"
#include <VersionHelpers.h>
// CImageButton
IMPLEMENT_DYNAMIC(CItemView, CButton)
CItemView::CItemView()
: m_images()
, m_bMouseTrack(TRUE)
{
m_textColor = RGB(10, 10, 10);
m_selectorCount = 0;
m_selectStaus = 0;
m_isCheck = false;
m_isRedDot = false;
m_isInit = false;
}
CItemView::~CItemView()
{
ReleaseImages();
}
BEGIN_MESSAGE_MAP(CItemView, CButton)
ON_WM_ERASEBKGND()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEHOVER()
ON_WM_MOUSELEAVE()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
static BOOL createFont(int size, CFont &font)
{
font.CreateFont(size, // 字体的高度
0, // 字体的宽度
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("微软雅黑"));
return true;
}
// CImageButton 消息处理程序
BOOL CItemView::OnEraseBkgnd(CDC* pDC)
{
// 处理背景擦除消息, 实现完全透明
return TRUE; // CButton::OnEraseBkgnd(pDC);
}
void CItemView::OnLButtonDown(UINT nFlags, CPoint point)
{
// 鼠标按下状态
m_state = Press;
if (m_itemValueRect.PtInRect(point)) {
if (!m_checkImage[0].IsNull()) {
//TRACE("on item value Rect.\n");
m_isCheck = !m_isCheck;
} else if (m_selectorCount > 0) {
m_selectStaus = 1;
//TRACE("on selector mouse down.\n");
}
}
Invalidate();
CButton::OnLButtonDown(nFlags, point);
}
void CItemView::OnLButtonUp(UINT nFlags, CPoint point)
{
m_state = Hover;
Invalidate();
CButton::OnLButtonUp(nFlags, point);
}
void CItemView::OnMouseMove(UINT nFlags, CPoint point)
{
// 追踪鼠标
if (m_bMouseTrack)
{
TRACKMOUSEEVENT eventTrack;
eventTrack.cbSize = sizeof(eventTrack);
eventTrack.dwFlags = TME_LEAVE | TME_HOVER;
eventTrack.hwndTrack = m_hWnd; // 指定要追踪的窗口
eventTrack.dwHoverTime = 1; // 鼠标在按钮上停留超过1ms, 才认为状态为HOVER
_TrackMouseEvent(&eventTrack); // 开启Windows的WM_MOUSELEAVE, WM_MOUSEHOVER事件支持
m_bMouseTrack = FALSE; // 若已经追踪, 则停止追踪
}
CButton::OnMouseMove(nFlags, point);
}
void CItemView::OnMouseHover(UINT nFlags, CPoint point)
{
m_state = Hover;
Invalidate();
CButton::OnMouseHover(nFlags, point);
}
void CItemView::OnMouseLeave()
{
m_state = Normal;
m_bMouseTrack = TRUE;
Invalidate();
CButton::OnMouseLeave();
}
void CItemView::PreSubclassWindow()
{
// 设置自绘模式
ModifyStyle(0, BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
#define PAD_RIGHT 15
#define PAD_LEFT 10
void CItemView::outputText(CDC *pDC, int nDpi)
{
CFont font;
CRect clientRC;
TEXTMETRICA metrics;
int offsetx, offsety;
GetClientRect(&clientRC);
const auto nItemWidth = clientRC.Width();
const auto nItemHeight = clientRC.Height();
UINT sizeFont = (UINT)(nItemHeight * 0.55);
sizeFont = sizeFont & (-2);
createFont(sizeFont, font);
pDC->SelectObject(&font);
pDC->SetBkMode(TRANSPARENT);
pDC->GetTextMetrics(&metrics);
offsetx = dp(50, nDpi);
{//text out item name.
CString cs;
GetWindowText(cs);
pDC->SetTextColor(m_textColor);
pDC->TextOut(offsetx, (nItemHeight - metrics.tmHeight) / 2, cs);
}
if (!m_valueString.IsEmpty()) {
CSize strSize;
strSize = pDC->GetTextExtent(m_valueString);
int offsetCheck = 0;
if (!m_checkImage[0].IsNull()) {
offsetCheck = dp(m_checkImage[0].GetWidth()+5, nDpi);
}
int offsetIcon = 0;
if (!m_rightIcon.IsNull()) {
offsetIcon = dp(m_rightIcon.GetWidth()+5, nDpi);
}
int offsetRedDot = 0;
if (m_isRedDot && !m_imgRedDot.IsNull()) {
offsetRedDot = dp(m_imgRedDot.GetWidth() + 5, nDpi);
}
offsetx = clientRC.Width() - strSize.cx * 1.05 - offsetCheck - offsetIcon - offsetRedDot - PAD_RIGHT;
pDC->SetTextColor(RGB(10, 10, 10));
pDC->TextOut(offsetx, (nItemHeight - metrics.tmHeight) / 2, m_valueString);
}
if (m_selectorCount > 0) {
sizeFont = (UINT)(nItemHeight * 0.46);
sizeFont = sizeFont & (-2);
CFont font2;
createFont(sizeFont, font2);
pDC->SelectObject(&font2);
pDC->GetTextMetrics(&metrics);
CSize strSize;
strSize = pDC->GetTextExtent(m_selectText);
offsetx = m_itemValueRect.left + (m_itemValueRect.Width() * 0.8 - strSize.cx)/2;
pDC->SetTextColor(RGB(10, 10, 10));
pDC->TextOut(offsetx, (nItemHeight - metrics.tmHeight) / 2, m_selectText);
}
}
void CItemView::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC memDc;
CBitmap bmp;
auto pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
if (lpDrawItemStruct->itemState & ODS_DISABLED) {
m_state = Disable;
}
CRect clientRC;
GetClientRect(&clientRC);
const auto nItemWidth = clientRC.Width();
const auto nItemHeight = clientRC.Height();
memDc.CreateCompatibleDC(pDC);
bmp.CreateCompatibleBitmap(pDC, nItemWidth, nItemHeight);
memDc.SelectObject(&bmp);
int nDpi = 96;
auto&& image = m_images[m_state];
if (!image.IsNull()) {
image.Draw(memDc, 0, 0, nItemWidth, nItemHeight, 0, 0, image.GetWidth(), image.GetHeight());
nDpi = MulDiv(nItemHeight,96,image.GetHeight());
}
if (!m_icon.IsNull()) {//icon
int w = dp(m_icon.GetWidth(), nDpi);
int h = dp(m_icon.GetHeight(), nDpi);
int x = dp(PAD_LEFT, nDpi);
int y = (clientRC.Height() - h) / 2;
m_icon.Draw(memDc, x, y, w, h,
0, 0, m_icon.GetWidth(), m_icon.GetHeight());
}
if (!m_checkImage[0].IsNull()) { //value
auto&& img = m_checkImage[m_isCheck];
int realW = dp(img.GetWidth(), nDpi);
int realH = dp(img.GetHeight(), nDpi);
m_itemValueRect.left = nItemWidth - dp((img.GetWidth() + PAD_RIGHT), nDpi);
m_itemValueRect.top = (nItemHeight - realH)/2;
m_itemValueRect.right = m_itemValueRect.left + realW;
m_itemValueRect.bottom = m_itemValueRect.top + realH;
img.Draw(memDc, m_itemValueRect.left, m_itemValueRect.top, realW, realH, 0, 0, img.GetWidth(), img.GetHeight());
}
if (!m_valueString.IsEmpty()) {
if (!m_rightIcon.IsNull()) {
int x = nItemWidth - dp(m_rightIcon.GetWidth() + PAD_RIGHT, nDpi);
int y = (nItemHeight - dp(m_rightIcon.GetHeight(), nDpi)) / 2;
int realW = dp(m_rightIcon.GetWidth(), nDpi);
int realH = dp(m_rightIcon.GetHeight(), nDpi);
m_rightIcon.Draw(memDc, x, y, realW, realH, 0, 0, m_rightIcon.GetWidth(), m_rightIcon.GetHeight());
}
if (m_isRedDot && !m_imgRedDot.IsNull()) {
int offsetIcon = 0;
if (!m_rightIcon.IsNull()) {
offsetIcon = m_rightIcon.GetWidth();
}
int x = nItemWidth - dp(m_imgRedDot.GetWidth() + offsetIcon + PAD_RIGHT, nDpi);
int y = (nItemHeight - dp(m_imgRedDot.GetHeight(), nDpi)) / 2;
int realW = dp(m_imgRedDot.GetWidth(), nDpi);
int realH = dp(m_imgRedDot.GetHeight(), nDpi);
m_imgRedDot.Draw(memDc, x, y, realW, realH, 0, 0, m_imgRedDot.GetWidth(), m_imgRedDot.GetHeight());
}
}
if (m_selectorCount > 0) {
CImage img;
LoadImageFromResource(img, m_selectBoxID);
int x = nItemWidth - dp(img.GetWidth() + PAD_RIGHT, nDpi);
int y = (nItemHeight - dp(img.GetHeight(), nDpi)) /2;
int realW = dp(img.GetWidth(), nDpi);
int realH = dp(img.GetHeight(), nDpi);
m_itemValueRect.left = x;
m_itemValueRect.top = y;
m_itemValueRect.right = m_itemValueRect.left + realW;
m_itemValueRect.bottom = m_itemValueRect.top + realH;
img.Draw(memDc, x, y, realW, realH, 0, 0, img.GetWidth(), img.GetHeight());
}
BLENDFUNCTION bf;
bf.AlphaFormat = AC_SRC_ALPHA;
bf.BlendFlags = 0;
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = 255;
pDC->AlphaBlend(0, 0, nItemWidth, nItemHeight, &memDc, 0, 0, nItemWidth, nItemHeight, bf);
outputText(pDC, nDpi);
bmp.DeleteObject();
memDc.DeleteDC();
}
void CItemView::SetImages(UINT nNormalId, UINT nHoverId, UINT nPressId, UINT nDisableId, LPCTSTR lpszResourceType)
{
ReleaseImages();
LoadImageFromResource(m_images[Normal], nNormalId, lpszResourceType);
LoadImageFromResource(m_images[Hover], nHoverId, lpszResourceType);
LoadImageFromResource(m_images[Press], nPressId, lpszResourceType);
nDisableId = nDisableId == 0 ? nNormalId : nDisableId;
LoadImageFromResource(m_images[Disable], nDisableId, lpszResourceType);
}
void CItemView::SetIcon(UINT nId)
{
LoadImageFromResource(m_icon, nId);
}
void CItemView::SetTextParam(COLORREF cr)
{
m_textColor = cr;
}
void CItemView::SetCheckImage(UINT nCheckId, UINT nNormalId)
{
LoadImageFromResource(m_checkImage[0], nNormalId);
LoadImageFromResource(m_checkImage[1], nCheckId);
}
void CItemView::SetValueString(CString &cs, int rightIconID, int redDotID)
{
m_valueString = cs;
if (rightIconID != -1) {
LoadImageFromResource(m_rightIcon, rightIconID);
}
if (redDotID != -1) {
LoadImageFromResource(m_imgRedDot, redDotID);
}
}
void CItemView::SetValueText(CString& str)
{
m_valueString = str;
if (m_isInit) {
Invalidate();
}
}
void CItemView::SetValueRedDot(BOOL on)
{
m_isRedDot = on;
if (m_isInit) {
Invalidate();
}
}
void CItemView::SetSelectValue(CString strInit, int itemCount, UINT boxID)
{
m_selectorCount = itemCount;
m_selectBoxID = boxID;
m_selectText = strInit;
}
void CItemView::SetSelectText(CString str)
{
m_selectText = str;
Invalidate();
}
int CItemView::GetSelectStaus()
{
int status = m_selectStaus;
m_selectStaus = 0;
return status;
}
void CItemView::LoadImageFromResource(CImage& img, UINT nId, LPCTSTR lpszResourceType)
{
const static auto hInstance = AfxGetInstanceHandle();
const auto hResource = ::FindResource(hInstance, MAKEINTRESOURCE(nId), lpszResourceType);
if (hResource == nullptr) {
return;
}
const auto hResData = LoadResource(hInstance, hResource);
if (hResData == nullptr) {
return;
}
const auto lpResource = LockResource(hResData);
if (lpResource == nullptr) {
return;
}
const auto dwResourceSize = SizeofResource(hInstance, hResource);
const auto hMem = GlobalAlloc(GMEM_FIXED, dwResourceSize);
if (hMem == nullptr) {
return;
}
const auto lpMem = static_cast<LPBYTE>(GlobalLock(hMem));
if (lpMem == nullptr) {
GlobalFree(hMem);
return;
}
memcpy(lpMem, lpResource, dwResourceSize);
const auto pStream = SHCreateMemStream(lpMem, dwResourceSize);
if (pStream != nullptr) {
if (img.Load(pStream) == S_OK && _tcsicmp(lpszResourceType, _T("PNG")) == 0 && img.GetBPP() == 32) {
// 预处理PNG的Alpha通道
for (auto i = 0; i < img.GetWidth(); i++) {
for (auto j = 0; j < img.GetHeight(); j++) {
const auto pColor = reinterpret_cast<LPBYTE>(img.GetPixelAddress(i, j));
pColor[0] = pColor[0] * pColor[3] / 255;
pColor[1] = pColor[1] * pColor[3] / 255;
pColor[2] = pColor[2] * pColor[3] / 255;
}
}
}
}
GlobalUnlock(lpMem);
GlobalFree(hMem);
}
void CItemView::ReleaseImages()
{
for (auto&& image : m_images)
{
image.Destroy();
}
}
BOOL CItemView::Create(LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
if (m_images[0].IsNull()) {
TRACE("CItemView::Create() fail\n");
return FALSE;
}
BOOL ret = CButton::Create(lpszCaption, dwStyle, rect, pParentWnd, nID);
m_isInit = true;
return ret;
}