欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > MFC 实现动态控件调整和主题切换

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

2024/12/31 1:25:31 来源:https://blog.csdn.net/m0_58648890/article/details/144566665  浏览:    关键词:MFC 实现动态控件调整和主题切换

在开发桌面应用程序时,常常需要为不同的屏幕分辨率和用户界面需求动态调整控件的大小,并且支持不同的主题(如浅色和深色模式)。在 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 基类。核心功能包括:

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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com