[例4.4] 实现类似Visual C++属性表中的钉子按钮

书籍:《Visual C++ 2017从入门到精通》

环境:visual studio 2022

内容:流程及源码分析

说明:以上内容大部分来自腾讯元宝。

新建对话框

一个简单的对话框程序-CSDN博客https://blog.csdn.net/qq_20725221/article/details/146396703?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522d04c513b850b06d55102b36c83a4515f%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=d04c513b850b06d55102b36c83a4515f&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-146396703-null-null.nonecase&utm_term=%E5%AF%B9%E8%AF%9D%E6%A1%86&spm=1018.2226.3001.4450

添加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);
}

 运行效果

未点击时效果

点击效果->窗口置顶

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值