MFC 实现动态控件调整和主题切换

news2024/12/19 23:46:00

在开发桌面应用程序时,常常需要为不同的屏幕分辨率和用户界面需求动态调整控件的大小,并且支持不同的主题(如浅色和深色模式)。在 MFC 中,虽然有一些方法来实现这些功能,但实现起来往往比较繁琐。为了更好地管理控件的调整和主题切换,我们可以创建一个通用的基类来处理这些功能。

1. 需求分析

在一个典型的应用程序中,可能会遇到以下几种需求:

  • 动态调整控件大小:当用户调整窗口大小时,控件的大小应根据窗口的变化自动调整。
  • 主题切换:根据用户的偏好或系统设置,切换浅色(Light)或深色(Dark)主题。

为了满足这些需求,我们可以设计一个 CBaseDlg 基类,该类继承自 CDialogEx,并提供以下功能:

  • 动态调整控件的大小。
  • 切换浅色和深色主题。

2. 设计 CBaseDlg 基类

首先,定义 CBaseDlg 类,该类提供了管理控件布局、字体、主题切换等功能。

头文件(CBaseDlg.h

#pragma once
#include <memory>
#include <unordered_map>

enum class ThemeType {
	Light,  // 浅色主题
	Dark    // 深色主题
};

struct Theme {
	COLORREF backgroundColor;
	COLORREF textColor;
	COLORREF buttonColor;
	COLORREF borderColor;
};

class CBaseDlg : public CDialogEx
{
	DECLARE_DYNAMIC(CBaseDlg)

public:
	CBaseDlg(UINT id, CWnd* pPage);				// 标准构造函数
	virtual ~CBaseDlg();						// 析构函数

	// 字体管理
	CFont* GetOrCreateFont(int nFontSize);		// 获取或创建字体
	void SetDefaultFont();						// 设置默认字体

	// 动态控件管理
	BOOL AddControl(UINT nCtrlID, CWnd* pControl);					// 添加控件
	BOOL RemoveControl(UINT nCtrlID);								// 移除控件
	BOOL UpdateControlText(UINT nCtrlID, const CString& strText);   // 更新控件文本
	CWnd* GetControl(UINT nCtrlID);									// 获取控件

	// 主题设置
	void SwitchTheme(ThemeType themeType);							// 切换主题

private:
	void AdjustControls(float dScaleX, float dScaleY);				// 调整控件大小
	void AdjustControlFont(CWnd* pWnd, int nWidth, int nHeight);	// 调整控件字体

private:
	bool m_bResizing;											// 控件是否正在调整大小
	int m_nInitialWidth;										// 对话框初始宽度
	int m_nInitialHeight;										// 对话框初始高度	
	std::unordered_map<int, CRect> m_mapCtrlLayouts;			// 控件布局
	std::map<UINT, std::unique_ptr<CWnd>> m_mapControls;		// 控件集合
	std::unordered_map<int, std::shared_ptr<CFont>> m_mapFonts;	// 控件字体

	DECLARE_MESSAGE_MAP()
public:
	virtual BOOL OnInitDialog();
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI);
	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
};

源文件(CBaseDlg.cpp

#include "stdafx.h"
#include "CBaseDlg.h"
#include "GridCtrl.h"
#include <windows.h>

// 全局主题对象
Theme g_lightTheme = { RGB(255, 255, 255), RGB(0, 0, 0), RGB(240, 240, 240), RGB(200, 200, 200) };
Theme g_darkTheme = { RGB(40, 40, 40), RGB(255, 255, 255), RGB(60, 60, 60), RGB(80, 80, 80) };

CFont g_defaultFont;
Theme* g_currentTheme = &g_lightTheme;

IMPLEMENT_DYNAMIC(CBaseDlg, CDialogEx)

CBaseDlg::CBaseDlg(UINT id, CWnd* pPage) : CDialogEx(id, pPage), m_bResizing(false)
{
	m_nInitialWidth = 0;
	m_nInitialHeight = 0;
}

CBaseDlg::~CBaseDlg()
{
	// shared_ptr会自动清理内存,不需要手动删除
	m_mapFonts.clear();
	m_mapCtrlLayouts.clear();
	m_mapControls.clear();
}

CFont* CBaseDlg::GetOrCreateFont(int nFontSize)
{
	auto it = m_mapFonts.find(nFontSize);
	if (it != m_mapFonts.end()) {
		return it->second.get();
	}

	// 使用 shared_ptr 来管理字体对象
	auto font = std::make_shared<CFont>();
	LOGFONT logFont = { 0 };
	_tcscpy_s(logFont.lfFaceName, _T("Segoe UI"));
	logFont.lfHeight = -nFontSize;
	logFont.lfQuality = CLEARTYPE_QUALITY;
	font->CreateFontIndirect(&logFont);
	m_mapFonts[nFontSize] = font;

	return font.get();
}

void CBaseDlg::SetDefaultFont()
{
	CFont* defaultFont = GetOrCreateFont(12);

	CWnd* pWnd = GetWindow(GW_CHILD);
	while (pWnd) {
		TCHAR szClassName[256];
		GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));
		if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
			pWnd = pWnd->GetNextWindow();
			continue;
		}

		pWnd->SetFont(defaultFont, TRUE);
		pWnd = pWnd->GetNextWindow();
	}
}

BOOL CBaseDlg::AddControl(UINT nCtrlID, CWnd* pControl)
{
	// 确保控件不重复添加
	if (m_mapControls.find(nCtrlID) != m_mapControls.end()) {
		return FALSE;  // 控件已经存在
	}

	m_mapControls[nCtrlID] = std::unique_ptr<CWnd>(pControl);
	return TRUE;
}

BOOL CBaseDlg::RemoveControl(UINT nCtrlID)
{
	auto it = m_mapControls.find(nCtrlID);
	if (it != m_mapControls.end()) {
		m_mapControls.erase(it);
		return TRUE;
	}
	return FALSE;
}

BOOL CBaseDlg::UpdateControlText(UINT nCtrlID, const CString& strText)
{
	auto it = m_mapControls.find(nCtrlID);
	if (it != m_mapControls.end()) {
		CWnd* pWnd = it->second.get();
		if (pWnd->GetSafeHwnd() != nullptr)
		{
			pWnd->SetWindowText(strText);
			return TRUE;
		}
	}
	return FALSE;
}

CWnd* CBaseDlg::GetControl(UINT nCtrlID)
{
	auto it = m_mapControls.find(nCtrlID);
	if (it != m_mapControls.end()) {
		return it->second.get();
	}
	return nullptr;
}

void CBaseDlg::SwitchTheme(ThemeType themeType)
{
	// 使用 map 来根据 themeType 查找主题
	static const std::unordered_map<ThemeType, Theme*> themeMap = {
		{ ThemeType::Light, &g_lightTheme },
		{ ThemeType::Dark, &g_darkTheme }
	};

	// 设置当前主题
	auto it = themeMap.find(themeType);
	if (it != themeMap.end()) {
		g_currentTheme = it->second;
	}
	else {
		g_currentTheme = &g_lightTheme;
	}

	// 更新控件的外观
	CWnd* pWnd = GetWindow(GW_CHILD);
	while (pWnd) {
		pWnd->Invalidate(); // 重绘控件
		pWnd = pWnd->GetNextWindow();
	}

	// 更新对话框背景颜色
	SetBackgroundColor(g_currentTheme->backgroundColor);
}

void CBaseDlg::AdjustControls(float dScaleX, float dScaleY)
{
	if (m_bResizing) return; // 防止在调整过程中重复调整

	m_bResizing = true;
	CWnd* pWnd = GetWindow(GW_CHILD);
	while (pWnd) {
		int nCtrlID = pWnd->GetDlgCtrlID();
		if (nCtrlID != -1 && m_mapCtrlLayouts.find(nCtrlID) != m_mapCtrlLayouts.end()) {
			CRect originalRect = m_mapCtrlLayouts[nCtrlID];
			CRect newRect(
				static_cast<int>(originalRect.left * dScaleX),
				static_cast<int>(originalRect.top * dScaleY),
				static_cast<int>(originalRect.right * dScaleX),
				static_cast<int>(originalRect.bottom * dScaleY));

			TCHAR szClassName[256];
			GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));

			if (_tcsicmp(szClassName, _T("ComboBox")) == 0) {
				CComboBox* pComboBox = (CComboBox*)pWnd;
				pComboBox->SetItemHeight(-1, newRect.Height());  // -1 表示所有项的高度
			}

			if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
				CGridCtrl* pGridCtrl = (CGridCtrl*)pWnd;
				pGridCtrl->SetDefCellHeight(newRect.Height() / 21);
				pGridCtrl->ExpandColumnsToFit(TRUE);
				pGridCtrl->ExpandLastColumn();
				pGridCtrl->Invalidate();
				pGridCtrl->UpdateWindow();
			}

			pWnd->MoveWindow(&newRect);
			AdjustControlFont(pWnd, newRect.Width(), newRect.Height());
		}
		pWnd = pWnd->GetNextWindow();
	}
	m_bResizing = false;
}

void CBaseDlg::AdjustControlFont(CWnd* pWnd, int nWidth, int nHeight)
{
	TCHAR szClassName[256];
	GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));

	if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
		return;
	}

	int fontSize = nHeight / 2;
	if (fontSize < 8) fontSize = 8;
	if (fontSize > 32) fontSize = 32;

	CFont* pFont = GetOrCreateFont(fontSize);

	pWnd->SetFont(pFont);
	pWnd->Invalidate(); // 刷新控件显示
}

BEGIN_MESSAGE_MAP(CBaseDlg, CDialogEx)
	ON_WM_SIZE()
	ON_WM_GETMINMAXINFO()
	ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

BOOL CBaseDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 获取当前语言
	LANGID langId = GetUserDefaultLangID();
	if (langId == LANG_CHINESE) {
		// 加载中文资源
	}
	else {
		// 加载英文资源
	}

	// 获取对话框的工作区(屏幕可用区域)
	CRect screenRect, dlgRect, clientRect;
	SystemParametersInfo(SPI_GETWORKAREA, 0, &screenRect, 0);
	GetClientRect(&clientRect);
	m_nInitialWidth = clientRect.Width();
	m_nInitialHeight = clientRect.Height();

	// 设置默认字体
	CFont* pDefaultFont = GetOrCreateFont(12);

	// 遍历子窗口(控件)
	CWnd* pWnd = GetWindow(GW_CHILD);
	while (pWnd) {
		int nCtrlID = pWnd->GetDlgCtrlID();
		if (nCtrlID != -1) {
			// 保存控件的初始布局
			CRect ctrlRect;
			pWnd->GetWindowRect(&ctrlRect);
			ScreenToClient(&ctrlRect);
			m_mapCtrlLayouts[nCtrlID] = ctrlRect;

			// 排除不需要操作的控件(如自定义控件 GridCtrl)
			TCHAR szClassName[256];
			GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));
			if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
				pWnd = pWnd->GetNextWindow();
				continue;
			}

			// 设置控件的默认字体
			pWnd->SetFont(pDefaultFont);
		}
		pWnd = pWnd->GetNextWindow();
	}

	// 将对话框居中
	GetWindowRect(&dlgRect);
	int dlgWidth = dlgRect.Width() * 2;
	int dlgHeight = dlgRect.Height() * 2;

	if (dlgWidth > screenRect.Width()) {
		dlgWidth = screenRect.Width();
	}
	if (dlgHeight > screenRect.Height()) {
		dlgHeight = screenRect.Height();
	}

	int centerX = screenRect.left + (screenRect.Width() - dlgWidth) / 2;
	int centerY = screenRect.top + (screenRect.Height() - dlgHeight) / 2;
	MoveWindow(centerX, centerY, dlgWidth, dlgHeight);

	return TRUE;
}

void CBaseDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialogEx::OnSize(nType, cx, cy);

	if (nType == SIZE_MINIMIZED || m_mapCtrlLayouts.empty()) {
		return;
	}

	// 检查尺寸变化是否足够大,避免频繁调整
	//static int lastWidth = 0, lastHeight = 0;
	//if (abs(cx - lastWidth) < 10 && abs(cy - lastHeight) < 10) {
	//	return;
	//}

	//lastWidth = cx;
	//lastHeight = cy;

	// 计算比例并调整布局
	float dScaleX = static_cast<float>(cx) / m_nInitialWidth;
	float dScaleY = static_cast<float>(cy) / m_nInitialHeight;

	AdjustControls(dScaleX, dScaleY);
}

void CBaseDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
	lpMMI->ptMinTrackSize.x = 400;
	lpMMI->ptMinTrackSize.y = 300;

	CDialogEx::OnGetMinMaxInfo(lpMMI);
}

HBRUSH CBaseDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	if (g_currentTheme) {
		pDC->SetBkColor(g_currentTheme->backgroundColor);
		pDC->SetTextColor(g_currentTheme->textColor);

		// 返回背景画刷
		return CreateSolidBrush(g_currentTheme->backgroundColor);
	}

	return CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
}

3. 总结

通过以上的代码,实现了一个支持动态调整控件大小和主题切换的 CBaseDlg 基类。核心功能包括:

  • 动态调整控件大小和字体。
  • 根据系统设置或用户选择切换浅色和深色主题。
  • 切换语言资源(待完成)

该类能够帮助开发者更加便捷地管理控件布局和主题切换,提升应用的用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262413.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT多线程(二):基于互斥锁与读写锁的线程同步

此处需要说明的是&#xff0c;这里的线程同步概念与操作系统中的线程同步并无区别&#xff0c;都是避免多个线程同时访问临界区数据可能产生的读写错误问题。在 Qt 中&#xff0c;有多个类可以实现线程同步的功能&#xff0c;这些类包括 QMutex、QMutexLocker、 QReadWriteLock…

【ChatGPT】解锁AI思维链:如何让机器像人类一样思考?

在人工智能领域&#xff0c;我们一直在追求让机器像人类一样思考。然而&#xff0c;即使是最先进的AI&#xff0c;也常常被诟病缺乏“常识”&#xff0c;难以理解复杂问题&#xff0c;更不用说像人类一样进行逻辑推理和解决问题了。最经常的表现就是遇到不会的地方&#xff0c;…

重新定义页签!Choerodon UI Tabs让管理更高效

01 引言 Tabs 组件通过提供平级区域&#xff0c;将大块内容进行有效的收纳和展现&#xff0c;从而保持界面整洁。但在企业应用的快速发展中&#xff0c;这样传统的页签组件已无法满足我们对界面布局和个性化展示的追求。Choerodon UI Tabs 组件通过支持多级分组、个性化配置、…

机器学习之偏差

机器学习中的偏差&#xff08;Bias&#xff09;是指模型的预测值与真实值之间的系统性误差&#xff0c;或者说模型无法准确捕捉数据中复杂模式的能力。偏差通常与模型的假设或学习能力有关&#xff0c;过高的偏差会导致模型的性能不佳&#xff0c;表现为欠拟合。 偏差的来源 模…

SSH连接监控以及新用户创建和系统资源访问限制

目录 监控连接数SSH连接数的限制和影响理论限制可能的影响 创建SSH新用户为每个ssh用户配置系统资源限制1. 使用 /etc/security/limits.conf 限制资源2. 使用 cgroups 控制资源3. 磁盘配额限制4. 限制 SSH 访问5. 使用 PAM 限制6. 监控脚本示例7. 设置定期任务清理8. 检查配置是…

测试工程师八股文04|计算机网络 和 其他

一、计算机网络 1、http和https的区别 HTTP和HTTPS是用于在互联网上传输数据的协议。它们都是应用层协议&#xff0c;建立在TCP/IP协议栈之上&#xff0c;用于客户端&#xff08;如浏览器&#xff09;和服务器之间的通信。 ①http和https的主要区别在于安全性。http是一种明…

单片机学习笔记——入门51单片机

一、单片机基础介绍 1.何为单片机 单片机&#xff0c;英文Micro Controller Unit&#xff0c;简称MCU 。内部集成了中央处理器CPU、随机存储器ROM、只读存储器RAM、定时器/计算器、中断系统和IO口等一系列电脑的常用硬件功能 单片机的任务是信息采集&#xff08;依靠传感器&a…

【青牛科技】D8563是低功耗的CMOS实时时钟/日历电路,它提供一个可编程时钟输出,一个中断输出和掉电检测器,所有的地址和数据通过IC总线接口串行传递。

概述&#xff1a; D8563是低功耗的CMOS实时时钟/日历电路,它提供一个可编程时钟输出&#xff0c;一个中断输出和掉电检测器&#xff0c;所有的地址和数据通过IC总线接口串行传递。最大总线速度为400Kbitss每次读写数据后&#xff0c;内嵌的字地址寄存器会自动产生增量。 主要特…

安卓获取所有可用摄像头并指定预览

在Android设备中&#xff0c;做预览拍照的需求的时候&#xff0c;我们会指定 CameraSelector DEFAULT_FRONT_CAMERA前置 或者后置CameraSelector DEFAULT_BACK_CAMERA 如果你使用的是平板或者工业平板&#xff0c;那么就会遇到多摄像头以及外置摄像头问题&#xff0c;简单的指…

R语言学习笔记-1

1. 基础操作和函数 清空环境&#xff1a;rm(list ls()) 用于清空当前的R环境。 打印输出&#xff1a;print("Hello, world") 用于输出文本到控制台。 查看已安装包和加载包&#xff1a; search()&#xff1a;查看当前加载的包。install.packages("package_na…

Windows如何安装go环境,离线安装beego

一、安装go 1、下载go All releases - The Go Programming Language 通过网盘分享的文件&#xff1a;分享的文件 链接: https://pan.baidu.com/s/1MCbo3k3otSoVdmIR4mpPiQ 提取码: hxgf 下载amd64.zip文件&#xff0c;然后解压到指定的路径 2、配置环境变量 需要新建两个环境…

Mac上使用ln指令创建软链接、硬链接

在Mac、Linux和Unix系统中&#xff0c;软连接&#xff08;Symbolic Link&#xff09;和硬连接&#xff08;Hard Link&#xff09;是两种不同的文件链接方式。它们的主要区别如下&#xff1a; 区别&#xff1a; 硬连接&#xff1a; 不能跨文件系统。不能链接目录&#xff08;为…

Unity A*算法实现+演示

注意&#xff1a; 本文是对基于下方文章链接的理论&#xff0c;并最终代码实现&#xff0c;感谢作者大大的描述&#xff0c;非常详细&#xff0c;流程稍微做了些改动&#xff0c;文末有工程网盘链接&#xff0c;感兴趣的可以下载。 A*算法详解(个人认为最详细,最通俗易懂的一…

博弈论3:图游戏SG函数(Graph Games)

目录 一、图游戏是什么 1.游戏特征 2.游戏实例 二、图游戏的必胜策略 1.SG 函数&#xff08;Sprague-Grundy Function&#xff09; 2.必胜策略&#xff08;利用SG函数&#xff09; 3.拿走游戏转化成图游戏&#xff08;Take-away Game -> Graph Game&#xff09; 一、图…

0101多级nginx代理websocket配置-nginx-web服务器

1. 前言 项目一些信息需要通过站内信主动推动给用户&#xff0c;使用websocket。web服务器选用nginx&#xff0c;但是域名是以前通过阿里云申请的&#xff0c;解析ip也是阿里云的服务器&#xff0c;甲方不希望更换域名。新的系统需要部署在内网服务器&#xff0c;简单拓扑图如…

qt-C++笔记之自定义类继承自 `QObject` 与 `QWidget` 及开发方式详解

qt-C笔记之自定义类继承自 QObject 与 QWidget 及开发方式详解 code review! 参考笔记 1.qt-C笔记之父类窗口、父类控件、对象树的关系 2.qt-C笔记之继承自 QWidget和继承自QObject 并通过 getWidget() 显示窗口或控件时的区别和原理 3.qt-C笔记之自定义类继承自 QObject 与 QW…

Elastic 8.17:Elasticsearch logsdb 索引模式、Elastic Rerank 等

作者&#xff1a;来自 Elastic Brian Bergholm 今天&#xff0c;我们很高兴地宣布 Elastic 8.17 正式发布&#xff01; 紧随一个月前发布的 Elastic 8.16 之后&#xff0c;我们将 Elastic 8.17 的重点放在快速跟踪关键功能上&#xff0c;这些功能将带来存储节省和搜索性能优势…

[C++]类的继承

一、什么是继承 1.定义&#xff1a; 在 C 中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;派生类&#xff09;继承另一个类&#xff08;基类&#xff09;的成员&#xff08;数据和函数&#xff09;。继承使得派生类能够直接访问基类的公有和保护成员&#xf…

Docker 用法详解

文章目录 一、Docker 快速入门1.1 部署 MYSQL1.2 命令解读&#xff1a; 二、Docker 基础2.1 常见命令&#xff1a;2.1.1 命令介绍&#xff1a;2.1.2 演示&#xff1a;2.1.3 命令别名&#xff1a; 2.2 数据卷&#xff1a;2.2.1 数据卷简介&#xff1a;2.2.2 数据卷命令&#xff…

【自动化】Python SeleniumUtil 油猴 工具 自动安装用户脚本

【自动化】Python SeleniumUtil 油猴 工具 【自动化】Python SeleniumUtil 工具-CSDN博客【自动化】Python SeleniumUtil 工具。https://blog.csdn.net/G971005287W/article/details/144565691 油猴工具 import timefrom selenium.webdriver.support.wait import WebDriverW…