Bootstrap

MFC 实现动态调整对话框控件与字体大小

在MFC开发中,为了提升用户体验,我们需要实现对话框窗口的动态调整功能,即用户通过拖动窗口的边框或角可以改变窗口大小,同时窗口中的控件位置、大小及字体也随之自适应变化。这篇文章将详细介绍如何实现这样的功能。

功能需求

  1. 对话框窗口大小的动态调整:用户可以拖动对话框边框或角调整窗口大小。
  2. 控件位置与大小的动态调整:窗口大小调整时,控件的位置与大小应按比例变化。
  3. 字体大小的动态调整:控件中的字体大小应随着控件的大小变化,保证界面美观,避免锯齿现象。
  4. 支持最小化与最大化:窗口需要支持最小化、最大化功能。
  5. 分页显示日志信息:界面包含一个表格,用于显示分页的日志信息,支持用户操作、翻页等功能。

实现步骤

以下是实现这些功能的详细步骤。

1. 创建MFC对话框工程

创建一个新的MFC对话框应用程序,命名为 SystemLogManager。在资源编辑器中添加控件,例如:

  • ComboBox:筛选条件(类型、用户等)。
  • DateTimePicker:时间筛选。
  • Edit:描述过滤的输入框。
  • Button:执行搜索及分页功能。
  • GridCtrl:显示日志信息的表格。

为每个控件分配唯一的 ID,例如 IDC_COMBO_TYPEIDC_BUTTON_SEARCH 等。


2. 初始化控件布局与字体

核心问题:控件需要随窗口调整而动态变化。

OnInitDialog 中:

  1. 遍历所有子控件,记录它们的初始布局(位置和大小)。
  2. 为所有控件设置默认字体。
  3. 设置对话框显示在屏幕中间。
代码实现:
BOOL CSystemLogManagerDlg::OnInitDialog()
{
	// 调用基类的 OnInitDialog 进行初始化
	CDialogEx::OnInitDialog();

	// 设置对话框标题
	SetWindowText(_T("系统运行日志"));

	// 初始化分页信息
	m_nCurrentPage = 1;  // 从第一页开始
	m_nTotalPages = 1;   // 默认总页数为 1

	// 初始化类型筛选下拉框
	m_comboType.AddString(_T("ALL"));       // 显示所有类型
	m_comboType.AddString(_T("信息"));      // 信息类型
	m_comboType.AddString(_T("操作"));      // 操作类型
	m_comboType.AddString(_T("错误"));      // 错误类型
	m_comboType.AddString(_T("未知"));      // 未知类型
	m_comboType.SetCurSel(0);               // 默认选中 "ALL"

	// 初始化用户筛选下拉框
	m_comboUser.AddString(_T("ALL"));       // 显示所有用户
	m_comboUser.AddString(_T("SYSTEM"));    // 系统用户
	auto usernames = UserManager::getInstance().getUsernames();  // 从用户管理器获取所有用户名
	for (const auto& username : usernames) {
		CString cstrUsername(username.c_str());
		m_comboUser.AddString(cstrUsername);
	}
	m_comboUser.SetCurSel(0);               // 默认选中 "ALL"

	// 设置日期筛选的默认起始时间为当前时间的 30 天前
	COleDateTime currentTime = COleDateTime::GetCurrentTime();  // 获取当前时间
	COleDateTime defaultStartTime = currentTime - COleDateTimeSpan(30, 0, 0, 0);  // 当前时间减去 30 天
	m_dateTimeStart.SetTime(defaultStartTime);  // 设置开始时间控件

	// 获取当前对话框的初始大小
	CRect screenRect, dlgRect, clientRect;
	GetClientRect(&clientRect);              // 获取客户区的大小
	m_nInitialWidth = clientRect.Width();    // 保存初始宽度
	m_nInitialHeight = clientRect.Height();  // 保存初始高度

	// 初始化默认字体
	CFont* defaultFont = GetOrCreateFont(12);  // 创建并获取默认字体,字体大小为 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;

			// 跳过特殊控件(如 MFCGridCtrl)
			TCHAR szClassName[256];
			GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));
			if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
				pWnd = pWnd->GetNextWindow();  // 获取下一个控件
				continue;
			}

			// 为控件设置默认字体
			pWnd->SetFont(defaultFont);
		}
		pWnd = pWnd->GetNextWindow();  // 获取下一个控件
	}

	// 获取屏幕和对话框大小
	GetWindowRect(&dlgRect);         // 获取对话框的屏幕坐标
	int dlgWidth = dlgRect.Width() * 2;  // 初始宽度放大 2 倍
	int dlgHeight = dlgRect.Height() * 2;  // 初始高度放大 2 倍

	// 限制对话框的大小不能超过屏幕工作区
	SystemParametersInfo(SPI_GETWORKAREA, 0, &screenRect, 0);  // 获取屏幕工作区大小
	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);  // 设置对话框位置和大小

	// 初始化系统日志管理器
	InitSystemLogManager();

	return TRUE;  // 返回 TRUE,表示焦点不会设置到控件
	// 如果返回 FALSE,则表示 OCX 属性页应该获得焦点
}


3. 动态调整控件布局

当窗口大小发生变化时:

  1. 计算窗口宽度和高度的比例变化。
  2. 遍历每个控件,根据比例调整控件的位置与大小。
  3. 更新字体大小,确保控件内容自适应。
关键函数:

控件调整核心逻辑:

void CSystemLogManagerDlg::AdjustControls(float dScaleX, float dScaleY)
{
    // 遍历所有子控件
    CWnd* pWnd = GetWindow(GW_CHILD);
    while (pWnd) {
        // 获取控件的 ID
        int nCtrlID = pWnd->GetDlgCtrlID();
        
        // 如果控件 ID 有效,并且已记录该控件的初始位置
        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));

            // 如果是下拉框(ComboBox),需要调整其项高度
            if (_tcsicmp(szClassName, _T("ComboBox")) == 0) {
                CComboBox* pComboBox = (CComboBox*)pWnd;
                pComboBox->SetItemHeight(-1, newRect.Height());  // -1 表示调整所有项的高度
            }

            // 如果是网格控件(MFCGridCtrl),需要额外调整网格布局
            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();
    }
}

字体大小调整:

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

	// 跳过特殊控件(如 MFCGridCtrl)
	if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
		return;
	}

	// 根据控件高度动态调整字体大小
	int fontSize = nHeight / 2;
	if (fontSize < 8) fontSize = 8;

	// 获取或创建字体
	CFont* pFont = GetOrCreateFont(fontSize);

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

4. 实现窗口的动态调整功能

需要处理 WM_SIZE 消息:

  1. 获取当前窗口大小。
  2. 计算宽度和高度的比例变化。
  3. 调用 AdjustControls 动态调整控件。
代码实现:
void CSystemLogManagerDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);

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

    float dScaleX = static_cast<float>(cx) / m_nInitialWidth;
    float dScaleY = static_cast<float>(cy) / m_nInitialHeight;

    AdjustControls(dScaleX, dScaleY);
}

5. 支持最小化、最大化

处理 WM_GETMINMAXINFO 消息,设置窗口的最小和最大大小:

void CSystemLogManagerDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    lpMMI->ptMinTrackSize.x = 400; // 最小宽度
    lpMMI->ptMinTrackSize.y = 300; // 最小高度

    CDialogEx::OnGetMinMaxInfo(lpMMI);
}

6. 完整代码

为方便参考,完整的头文件和实现文件如下:

SystemLogManagerDlg.h
#pragma once
#include "afxdialogex.h"
#include "GridCtrl.h"

// CSystemLogManagerDlg 对话框

class CSystemLogManagerDlg : public CDialogEx
{
	DECLARE_DYNAMIC(CSystemLogManagerDlg)

public:
	CSystemLogManagerDlg(CWnd* pParent = nullptr);   // 标准构造函数
	virtual ~CSystemLogManagerDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DIALOG_SYSTEM_LOG_MANAGER };
#endif

private:
	void InitSystemLogManager();
	void FillSystemLogManager();
	CFont* GetOrCreateFont(int nFontSize);
	void UpdatePageInfo();
	void SetDefaultFont();
	void AdjustControls(float dScaleX, float dScaleY);
	void AdjustControlFont(CWnd* pWnd, int nWidth, int nHeight);
	void AdjustComboBoxStyle(CComboBox& comboBox);
	void AdjustDateTimeCtrlStyle(CDateTimeCtrl& dateTimeCtrl);

private:
	int m_nInitialWidth;   // 初始宽度
	int m_nInitialHeight;  // 初始高度
	int m_nCurrentPage;    // 当前页码
	int m_nTotalPages;     // 总页数
	std::map<int, CRect> m_mapCtrlLayouts;	// 存储控件的初始布局信息
	std::map<int, CFont*> m_mapFonts;		// 管理字体的容器,键为字体大小

private:
	CComboBox m_comboType;
	CComboBox m_comboUser;
	CDateTimeCtrl m_dateTimeStart;
	CDateTimeCtrl m_dateTimeEnd;
	CEdit m_editDescription;
	CGridCtrl m_listLogs;
	CStatic m_staticPageNum;

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

	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 void OnBnClickedButtonSearch();
	afx_msg void OnBnClickedButtonPrevPage();
	afx_msg void OnBnClickedButtonNextPage();
	afx_msg void OnSelchangeComboType();
	afx_msg void OnSelchangeComboUser();
};

SystemLogManagerDlg.cpp
// SystemLogManagerDlg.cpp: 实现文件
//

#include "stdafx.h"
#include "BondEq.h"
#include "afxdialogex.h"
#include "SystemLogManagerDlg.h"


// CSystemLogManagerDlg 对话框

IMPLEMENT_DYNAMIC(CSystemLogManagerDlg, CDialogEx)

CSystemLogManagerDlg::CSystemLogManagerDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG_SYSTEM_LOG_MANAGER, pParent)
{
	m_nInitialWidth = 0;
	m_nInitialHeight = 0;
}

CSystemLogManagerDlg::~CSystemLogManagerDlg()
{
	for (auto& pair : m_mapFonts) {
		if (pair.second) {
			pair.second->DeleteObject();
			delete pair.second;
		}
	}
	m_mapFonts.clear();
}

void CSystemLogManagerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_COMBO_TYPE, m_comboType);
	DDX_Control(pDX, IDC_COMBO_USER, m_comboUser);
	DDX_Control(pDX, IDC_DATETIMEPICKER_START, m_dateTimeStart);
	DDX_Control(pDX, IDC_DATETIMEPICKER_END, m_dateTimeEnd);
	DDX_Control(pDX, IDC_EDIT_DESCRIPTION, m_editDescription);
	DDX_Control(pDX, IDC_CUSTOM_LIST_LOGS, m_listLogs);
	DDX_Control(pDX, IDC_STATIC_PAGE_NUMBER, m_staticPageNum);
}

void CSystemLogManagerDlg::InitSystemLogManager()
{
	if (m_listLogs.GetSafeHwnd() == NULL)
		return;

	int nRows = 21; // 包括表头(1 行)和数据(20 行)
	int nCols = 5;

	int nFixRows = 1;
	int nFixCols = 0;
	int nRowIdx = 0;
	int nColIdx = 0;

	m_listLogs.DeleteAllItems();
	m_listLogs.SetVirtualMode(FALSE);
	m_listLogs.GetDefaultCell(TRUE, FALSE)->SetBackClr(g_nGridFixCellColor); // 设置固定行背景色
	m_listLogs.GetDefaultCell(FALSE, TRUE)->SetBackClr(g_nGridFixCellColor); // 设置固定列背景色
	m_listLogs.GetDefaultCell(FALSE, FALSE)->SetBackClr(g_nGridCellColor);	// 设置单元格背景色
	m_listLogs.SetFixedTextColor(g_nGridFixFontColor); // 设置固定行列字体颜色

	m_listLogs.SetRowCount(nRows);
	m_listLogs.SetColumnCount(nCols);
	m_listLogs.SetFixedRowCount(nFixRows);
	m_listLogs.SetFixedColumnCount(nFixCols);

	// Col
	m_listLogs.SetColumnWidth(nColIdx, 10);
	m_listLogs.SetItemText(nRowIdx, nColIdx++, _T("No."));
	m_listLogs.SetColumnWidth(nColIdx, 10);
	m_listLogs.SetItemText(nRowIdx, nColIdx++, _T("类型"));
	m_listLogs.SetColumnWidth(nColIdx, 100);
	m_listLogs.SetItemText(nRowIdx, nColIdx++, _T("事件"));
	m_listLogs.SetColumnWidth(nColIdx, 30);
	m_listLogs.SetItemText(nRowIdx, nColIdx++, _T("用户"));
	m_listLogs.SetColumnWidth(nColIdx, 50);
	m_listLogs.SetItemText(nRowIdx, nColIdx++, _T("时间"));

	// 创建 20 行空白数据行
	for (int i = 1; i < nRows; ++i) {
		for (int j = 0; j < nCols; ++j) {
			m_listLogs.SetItemText(i, j, _T("")); // 初始化为空字符串
		}
	}

	m_listLogs.SetFixedRowSelection(FALSE);
	m_listLogs.SetFixedColumnSelection(FALSE);
	m_listLogs.SetEditable(FALSE);
	m_listLogs.SetRowResize(FALSE);
	m_listLogs.SetColumnResize(TRUE);
	m_listLogs.ExpandColumnsToFit(TRUE);
	m_listLogs.SetListMode(TRUE);				// 启用列表模式
	m_listLogs.EnableSelection(TRUE);			// 启用选择
	m_listLogs.SetSingleRowSelection(TRUE);		// 自动整行高亮(限制为单行选择)
	m_listLogs.ExpandLastColumn();				// 最后一列填充网格

	try {
		FillSystemLogManager();
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("初始化运行日志失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}

void CSystemLogManagerDlg::FillSystemLogManager()
{
	// 获取筛选条件
	CString selectedType, selectedUser, description;
	m_comboType.GetLBText(m_comboType.GetCurSel(), selectedType);
	m_comboUser.GetLBText(m_comboUser.GetCurSel(), selectedUser);
	m_editDescription.GetWindowText(description);

	COleDateTime startTime, endTime;
	m_dateTimeStart.GetTime(startTime);
	m_dateTimeEnd.GetTime(endTime);
	CString strStartTime = startTime.Format(_T("%Y-%m-%d %H:%M:%S"));
	CString strEndTime = endTime.Format(_T("%Y-%m-%d %H:%M:%S"));

	std::string type = CT2A(selectedType);
	std::string user = CT2A(selectedUser);
	std::string desc = CT2A(description);
	std::string start = CT2A(strStartTime);
	std::string end = CT2A(strEndTime);

	// 获取日志管理实例
	SystemLogManager& logManager = SystemLogManager::getInstance();

	int pageSize = 20; // 每页显示 20 条记录
	int totalRecords = logManager.getTotalLogCount(type, user, desc, start, end);
	m_nTotalPages = (totalRecords + pageSize - 1) / pageSize;
	auto logs = logManager.getFilteredLogs(type, user, desc, start, end, m_nCurrentPage, pageSize);

	// 更新表格数据
	int rowIdx = 1;
	for (const auto& log : logs) {
		m_listLogs.SetItemText(rowIdx, 0, CString(std::to_string(rowIdx).c_str())); // 序号
		m_listLogs.SetItemText(rowIdx, 1, CString(log[1].c_str()));                 // 类型
		m_listLogs.SetItemText(rowIdx, 2, CString(log[2].c_str()));                 // 事件
		m_listLogs.SetItemText(rowIdx, 3, CString(log[3].c_str()));                 // 用户
		m_listLogs.SetItemText(rowIdx, 4, CString(log[4].c_str()));                 // 时间
		++rowIdx;
	}

	// 清空多余行
	for (; rowIdx <= 20; ++rowIdx) {
		m_listLogs.SetItemText(rowIdx, 0, CString(std::to_string(rowIdx).c_str())); // 序号列
		for (int colIdx = 1; colIdx < m_listLogs.GetColumnCount(); ++colIdx) {
			m_listLogs.SetItemText(rowIdx, colIdx, _T(""));
		}
	}

	m_listLogs.ExpandColumnsToFit(FALSE);
	m_listLogs.ExpandLastColumn();
	m_listLogs.Invalidate();
	m_listLogs.UpdateWindow();

	UpdatePageInfo();
}

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

	CFont* font = new 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;
}

void CSystemLogManagerDlg::UpdatePageInfo()
{
	// 格式化页码信息为 "当前页/总页数"
	CString pageInfo;
	pageInfo.Format(_T("%d/%d 页"), m_nCurrentPage, m_nTotalPages);
	m_staticPageNum.SetWindowText(pageInfo);
}

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

	// 遍历所有控件,应用默认字体
	CWnd* pWnd = GetWindow(GW_CHILD);
	while (pWnd) {
		// 跳过特殊控件(如 MFCGridCtrl)
		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();
	}
}

void CSystemLogManagerDlg::AdjustControls(float dScaleX, float dScaleY)
{
	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();
	}
}

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

	// 跳过特殊控件(如 MFCGridCtrl)
	if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
		return;
	}

	// 根据控件高度动态调整字体大小
	int fontSize = nHeight / 2;
	if (fontSize < 8) fontSize = 8;

	// 获取或创建字体
	CFont* pFont = GetOrCreateFont(fontSize);

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


void CSystemLogManagerDlg::AdjustComboBoxStyle(CComboBox& comboBox)
{
	DWORD dwStyle = comboBox.GetStyle();
	comboBox.ModifyStyle(0, CBS_DROPDOWNLIST | CBS_HASSTRINGS | CBS_OWNERDRAWFIXED);

	comboBox.Invalidate();
	comboBox.UpdateWindow();
}


void CSystemLogManagerDlg::AdjustDateTimeCtrlStyle(CDateTimeCtrl& dateTimeCtrl)
{
	dateTimeCtrl.ModifyStyle(0, DTS_RIGHTALIGN);
	dateTimeCtrl.Invalidate();
	dateTimeCtrl.UpdateWindow();
}


BEGIN_MESSAGE_MAP(CSystemLogManagerDlg, CDialogEx)
	ON_BN_CLICKED(IDC_BUTTON_SEARCH, &CSystemLogManagerDlg::OnBnClickedButtonSearch)
	ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CSystemLogManagerDlg::OnBnClickedButtonPrevPage)
	ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CSystemLogManagerDlg::OnBnClickedButtonNextPage)
	ON_CBN_SELCHANGE(IDC_COMBO_TYPE, &CSystemLogManagerDlg::OnSelchangeComboType)
	ON_CBN_SELCHANGE(IDC_COMBO_USER, &CSystemLogManagerDlg::OnSelchangeComboUser)
	ON_WM_SIZE()
	ON_WM_GETMINMAXINFO()
END_MESSAGE_MAP()


// CSystemLogManagerDlg 消息处理程序


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

	// TODO:  在此添加额外的初始化
	SetWindowText(_T("系统运行日志"));

	m_nCurrentPage = 1;  // 从第一页开始
	m_nTotalPages = 1;   // 默认总页数为 1

	m_comboType.AddString(_T("ALL"));
	m_comboType.AddString(_T("信息"));
	m_comboType.AddString(_T("操作"));
	m_comboType.AddString(_T("错误"));
	m_comboType.AddString(_T("未知"));
	m_comboType.SetCurSel(0);

	m_comboUser.AddString(_T("ALL"));
	m_comboUser.AddString(_T("SYSTEM"));
	auto usernames = UserManager::getInstance().getUsernames();
	for (const auto& username : usernames) {
		CString cstrUsername(username.c_str());
		m_comboUser.AddString(cstrUsername);
	}
	m_comboUser.SetCurSel(0);
	
	// 设置为 30 天前
	COleDateTime currentTime = COleDateTime::GetCurrentTime();
	COleDateTime defaultStartTime = currentTime - COleDateTimeSpan(30, 0, 0, 0);
	m_dateTimeStart.SetTime(defaultStartTime);

	CRect screenRect, dlgRect, clientRect;
	GetClientRect(&clientRect);
	m_nInitialWidth = clientRect.Width();
	m_nInitialHeight = clientRect.Height();

	// 初始化默认字体
	CFont* defaultFont = 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;

			// 跳过特殊控件(如 MFCGridCtrl)
			TCHAR szClassName[256];
			GetClassName(pWnd->m_hWnd, szClassName, sizeof(szClassName));
			if (_tcsicmp(szClassName, _T("MFCGridCtrl")) == 0) {
				pWnd = pWnd->GetNextWindow();
				continue;
			}

			// 设置默认字体
			pWnd->SetFont(defaultFont);
		}
		pWnd = pWnd->GetNextWindow();
	}

	GetWindowRect(&dlgRect);
	int dlgWidth = dlgRect.Width() * 2;
	int dlgHeight = dlgRect.Height() * 2;

	SystemParametersInfo(SPI_GETWORKAREA, 0, &screenRect, 0);
	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);

	InitSystemLogManager();

	return TRUE;  // return TRUE unless you set the focus to a control
	// 异常: OCX 属性页应返回 FALSE
}


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

	// TODO: 在此处添加消息处理程序代码
	if (nType == SIZE_MINIMIZED || m_mapCtrlLayouts.empty()) {
		return;
	}

	float dScaleX = static_cast<float>(cx) / m_nInitialWidth;
	float dScaleY = static_cast<float>(cy) / m_nInitialHeight;

	// 遍历对话框中的所有控件
	AdjustControls(dScaleX, dScaleY);
}


void CSystemLogManagerDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	lpMMI->ptMinTrackSize.x = 400; // 最小宽度
	lpMMI->ptMinTrackSize.y = 300; // 最小高度

	CDialogEx::OnGetMinMaxInfo(lpMMI);
}


void CSystemLogManagerDlg::OnBnClickedButtonSearch()
{
	// TODO: 在此添加控件通知处理程序代码
	try {
		m_nCurrentPage = 1;
		FillSystemLogManager();
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("搜索失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}


void CSystemLogManagerDlg::OnBnClickedButtonPrevPage()
{
	// TODO: 在此添加控件通知处理程序代码
	try {
		if (m_nCurrentPage > 1) {
			m_nCurrentPage--;
			FillSystemLogManager();
		}
		else {
			AfxMessageBox(_T("已经是第一页!"));
		}
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("切换到上一页失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}


void CSystemLogManagerDlg::OnBnClickedButtonNextPage()
{
	// TODO: 在此添加控件通知处理程序代码
	try {
		if (m_nCurrentPage < m_nTotalPages) {
			m_nCurrentPage++;
			FillSystemLogManager();
		}
		else {
			AfxMessageBox(_T("已经是最后一页!"));
		}
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("切换到下一页失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}


void CSystemLogManagerDlg::OnSelchangeComboType()
{
	// TODO: 在此添加控件通知处理程序代码
	try {
		m_nCurrentPage = 1;
		FillSystemLogManager();
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("切换类型失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}


void CSystemLogManagerDlg::OnSelchangeComboUser()
{
	// TODO: 在此添加控件通知处理程序代码
	try {
		m_nCurrentPage = 1;
		FillSystemLogManager();
	}
	catch (const std::exception& ex) {
		CString errorMsg;
		errorMsg.Format(_T("切换角色失败:%s"), CString(ex.what()));
		AfxMessageBox(errorMsg, MB_ICONERROR);
	}
}

结论

通过本文详细的实现步骤和完整代码,您可以轻松地在MFC项目中实现对话框的动态调整功能,包括控件布局和字体的自适应变化。这种动态适配的实现方式可以广泛应用于需要灵活界面的桌面软件开发中。

;