书籍:《Visual C++ 2017从入门到精通》
环境:visual studio 2022
内容:流程及源码分析
说明:以上内容大部分来自腾讯元宝。
新建对话框
添加CMyButton类
类视图->添加->类
拷贝资源
加入资源到工程
资源视图->右击Test404.rc->添加
添加成功效果
修改资源ID
为CMyButton类添加以下成员
CMyButton类中重载PreSubclassWindow()和DrawItem()
为CButton类添加LBUTTONDOWN消息的处理函数OnLButtonDown()
添加按钮,变量,及修改属性
Owner Draw(所有者描述)属性设置为true
源码分析
CMyButton.h
#pragma once
#include <afxwin.h>
class CMyButton :
public CButton
{
public:
CDC memdc, memdc2;
CBitmap bitmap, bitmap2;
BOOL m_state;
CMyButton();
virtual ~CMyButton();
virtual void PreSubclassWindow();
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};
CMyButton.cpp
#include "pch.h"
#include "CMyButton.h"
#include "Resource.h"
CMyButton::CMyButton()
{
}
CMyButton::~CMyButton()
{
if (memdc.m_hDC != NULL)
memdc.DeleteDC();
if (memdc2.m_hDC != NULL)
memdc2.DeleteDC();
bitmap.DeleteObject();
bitmap2.DeleteObject();
}
//此代码通过 PreSubclassWindow 实现了按钮位图资源的预加载和内存 DC 的初始化,为自定义绘制奠定了基础。需补充资源释放逻辑,并结合状态管理完善绘制功能,最终实现高效、美观的自绘按钮。
void CMyButton::PreSubclassWindow()
{
// TODO: 在此添加专用代码和/或调用基类
/*状态初始化
用于记录按钮当前状态(如正常、按下、悬停),需在 DrawItem 中根据此状态切换绘制逻辑。*/
m_state = FALSE; // 初始化按钮状态为“未按下”
/*位图资源加载
作用:为按钮不同状态(如默认、按下)准备图像资源。
注意:需确保资源 ID IDB_NAILIN 和 IDB_NAILON 存在且为 16 位或 32 位位图。*/
bitmap.LoadBitmapW(IDB_NAILIN); // 加载默认状态位图
bitmap2.LoadBitmapW(IDB_NAILON); // 加载选中/按下状态位图
/*设备上下文(DC)与内存 DC 操作
目的:通过内存 DC 预加载位图,避免直接在屏幕 DC 上操作导致的闪烁。
潜在问题:
未释放 GetDC() 获取的 DC,需补充 ReleaseDC(pDC)。
内存 DC 未在析构函数中删除,可能造成内存泄漏。*/
CDC* pDC = GetDC(); // 获取控件设备上下文
memdc.CreateCompatibleDC(pDC); // 创建兼容内存DC
memdc.SelectObject(&bitmap); // 将位图选入内存DC
memdc2.CreateCompatibleDC(pDC); // 第二个内存DC
memdc2.SelectObject(&bitmap2); // 选入第二张位图
CButton::PreSubclassWindow(); // 调用基类实现
}
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: 添加您的代码以绘制指定项
/*获取绘制区域与设备上下文
rcItem 定义了按钮的绘制范围,确保绘制内容不超出按钮边界。
hDC 是按钮的客户区设备上下文,所有绘制操作需基于此 DC 完成。*/
CRect client = lpDrawItemStruct->rcItem; // 获取按钮客户区矩形
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); // 获取设备上下文
DWORD state = lpDrawItemStruct->itemState; // 获取按钮状态(如选中、焦点等)
// 填充按钮背景色(使用系统按钮面板的颜色)
//使用系统颜色 COLOR_BTNFACE 填充背景,保持与系统风格一致。
CBrush brush;
brush.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
pDC->FillRect(client, &brush);
// 根据状态绘制不同位图
/*根据 m_state(按钮状态标志)选择不同位图:
m_state = TRUE:绘制按下状态位图(memdc)。
m_state = FALSE:绘制默认状态位图(memdc2)。
位图尺寸硬编码:24x21 可能导致高分辨率屏幕显示模糊,建议动态获取位图尺寸。*/
if (m_state)
{
pDC->StretchBlt(client.left, client.top, client.Width(),
client.Height(), &memdc, 0, 0, 24, 21, SRCCOPY); // 绘制按下状态位图
GetParent()->SetWindowPos(&CWnd::wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 父窗口置顶
}
else
{
pDC->StretchBlt(client.left, client.top, client.Width(),
client.Height(), &memdc2, 0, 0, 24, 21, SRCCOPY); // 绘制默认状态位图
GetParent()->SetWindowPos(&CWnd::wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 恢复父窗口层级
}
// 填充背景后释放画刷
brush.DeleteObject();
}
BEGIN_MESSAGE_MAP(CMyButton, CButton)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_state = !m_state; //更新按钮状态。
CButton::OnLButtonDown(nFlags, point);
}
运行效果
未点击时效果
点击效果->窗口置顶