MFC 自定义网格控件

devtools/2024/12/24 16:54:32/

一、什么是 Custom Control?

Custom Control(自定义控件) 是 MFC(Microsoft Foundation Classes)框架中提供的一种控件类型,用于实现自定义的外观和功能。当标准控件(例如 CEditCButtonCListCtrl 等)无法满足特定需求时,可以使用 Custom Control 来实现个性化的控件。

Custom Control 的核心特点:

  • 基于 CWnd:通过继承 CWnd 类,实现完全自定义的绘制和行为。
  • 灵活性高:开发者可以自定义控件的绘制外观、消息响应、用户交互等。
  • 适用于特殊需求:如自定义按钮、进度条、图表、绘图区域等。

二、Custom Control 的使用方法

1. 在资源编辑器中添加 Custom Control

  • 打开 资源视图,编辑对应的对话框模板。
  • 从工具箱中选择 Custom Control,并放置到对话框上。
  • 在控件的 属性窗口 中:
    • Class:指定自定义控件的类名(如 MyCustomControl)。
    • ID:设置控件的唯一标识符(如 IDC_MY_CUSTOM)。

2. 创建自定义控件类

要实现自定义控件,需要创建一个继承自 CWnd 的类,并重写消息处理函数。

示例代码:

头文件:MyCustomControl.h

#pragma once
#include "afxwin.h"class CMyCustomControl : public CWnd
{
public:CMyCustomControl();virtual ~CMyCustomControl();protected:DECLARE_MESSAGE_MAP()afx_msg void OnPaint();                // 自定义绘制afx_msg void OnLButtonDown(UINT nFlags, CPoint point); // 鼠标点击事件
};

实现文件:MyCustomControl.cpp

#include "pch.h"
#include "MyCustomControl.h"BEGIN_MESSAGE_MAP(CMyCustomControl, CWnd)ON_WM_PAINT()ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()CMyCustomControl::CMyCustomControl() {}CMyCustomControl::~CMyCustomControl() {}void CMyCustomControl::OnPaint()
{CPaintDC dc(this); // 设备上下文CRect rect;GetClientRect(&rect);dc.FillSolidRect(rect, RGB(240, 240, 240)); // 背景颜色dc.SetTextColor(RGB(0, 0, 255));           // 文本颜色dc.DrawText(_T("自定义控件示例"), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}void CMyCustomControl::OnLButtonDown(UINT nFlags, CPoint point)
{AfxMessageBox(_T("自定义控件被点击!"));CWnd::OnLButtonDown(nFlags, point);
}

3. 在对话框类中绑定 Custom Control

在对话框类中,将自定义控件绑定到资源中的 Custom Control。

示例代码:

头文件:MyDialog.h

#include "MyCustomControl.h"class CMyDialog : public CDialogEx
{...
private:CMyCustomControl m_myCustomControl; // 自定义控件对象
};

DoDataExchange 绑定控件:

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{CDialogEx::DoDataExchange(pDX);DDX_Control(pDX, IDC_MY_CUSTOM, m_myCustomControl); // 控件绑定
}

4. 在代码中动态创建 Custom Control

除了在资源编辑器中定义,Custom Control 也可以在代码中动态创建:

BOOL CMyDialog::OnInitDialog()
{CDialogEx::OnInitDialog();CRect rect(10, 10, 200, 50); // 控件位置和大小m_myCustomControl.Create(NULL, WS_CHILD | WS_VISIBLE, rect, this, 1234);return TRUE;
}

三、CGridCtrl 简介

CGridCtrl 是基于 Custom Control 的 MFC 自定义网格控件,通常用于显示表格数据。它是 CodeProject 社区提供的开源组件,可以实现类似 Excel 风格的表格功能。

CGridCtrl 官网:https://www.codeproject.com/Articles/8/MFC-Grid-control#History

CGridCtrl 的核心功能包括:

  1. 行列管理:动态添加、删除行列。
  2. 单元格管理:设置单元格文本、背景色、字体、边框等。
  3. 选择模式:支持单行选择、多行选择、单元格选择等。
  4. 数据编辑:单元格支持编辑功能,启用列表模式。
  5. 只读单元格:特定单元格可设置为只读,防止用户修改。
  6. 自适应列宽:支持自动扩展列宽适配内容。
  7. 高亮与排序:自动高亮选中行,并支持表格数据排序。

四、CGridCtrl 使用注意事项

1. SetItemTextSetRowCount 的关系

调用 SetItemText 函数设置单元格内容时,必须确保指定的行索引不超过当前的 SetRowCount 设置的行数。如果超出设置的行数范围,单元格内容将不会显示,也不会自动扩展行数。

2. 调用 ExpandColumnsToFitExpandLastColumn

在填充表格数据时,如果表格出现滚动条,默认情况下最后一列的宽度可能无法正确填充,导致内容显示不全或者异常。为确保显示正常,必须调用:

  • ExpandColumnsToFit:调整所有列的宽度,使其适应当前表格的可视区域。
  • ExpandLastColumn:将最后一列扩展到表格的剩余宽度,确保视觉上的完整性。

五、CGridCtrl 特殊单元格类型使用

1. 设置下拉框单元格(CGridCellCombo)

通过设置单元格类型为 CGridCellCombo,可以实现在单元格中显示下拉框,用户可选择选项。

示例代码:

if (m_gridUserManager.SetCellType(i, 3, RUNTIME_CLASS(CGridCellCombo))) {CGridCellCombo* pCell = static_cast<CGridCellCombo*>(m_gridUserManager.GetCell(i, 3));pCell->SetOptions(permissions); // 设置下拉框选项列表pCell->SetStyle(CBS_DROPDOWNLIST); // 设置为只可选择的下拉框
}
  • SetOptions:设置下拉框的选项内容。
  • SetStyle(CBS_DROPDOWNLIST):设置为下拉选择模式,用户不能输入自由文本。

2. 设置复选框单元格(CGridCellCheck)

通过设置单元格类型为 CGridCellCheck,可以在单元格中显示复选框,用户可勾选。

示例代码:

if (m_grid.SetCellType(nRowIndex, 1, RUNTIME_CLASS(CGridCellCheck))) {auto* pCell = static_cast<CGridCellCheck*>(m_grid.GetCell(nRowIndex, 1));pCell->SetCheck(TRUE); // 设置复选框为选中状态
}
  • SetCheck(TRUE):设置复选框的初始状态为选中。
  • 通过 SetCheck(FALSE) 可以将复选框状态设置为未选中。

注意事项:

  • 在调用 SetCellType 之前,需要确保表格已设置足够的行列数。
  • 通过 RUNTIME_CLASS 设置单元格类型时,需要确保所用的单元格类(如 CGridCellComboCGridCellCheck)已经包含在项目中。

六、CGridCtrl 使用示例

1. 初始化表格控件

Custom Control 指定自定义控件的类名(MFCGridCtrl)。
以下代码展示如何初始化一个 CGridCtrl 控件,创建 4 列表格,并设置标题和基础样式:

void CRecipeListDlg::InitRecipeLise()
{if (m_grid.GetSafeHwnd() == NULL) {return;}int nRows = 1;int nCols = 4;int nFixRows = 1;int nFixCols = 0;int nRowIdx = 0;int nColIdx = 0;m_grid.DeleteAllItems();m_grid.SetVirtualMode(FALSE);m_grid.GetDefaultCell(TRUE, FALSE)->SetBackClr(g_nGridFixCellColor); // 设置固定行背景色m_grid.GetDefaultCell(FALSE, TRUE)->SetBackClr(g_nGridFixCellColor); // 设置固定列背景色m_grid.GetDefaultCell(FALSE, FALSE)->SetBackClr(g_nGridCellColor);	 // 设置单元格背景色m_grid.SetFixedTextColor(g_nGridFixFontColor);						 // 设置固定行列字体颜色m_grid.SetRowCount(nRows);m_grid.SetColumnCount(nCols);m_grid.SetFixedRowCount(nFixRows);m_grid.SetFixedColumnCount(nFixCols);// Colm_grid.SetColumnWidth(nColIdx, 10);m_grid.SetItemText(nRowIdx, nColIdx++, _T("No."));m_grid.SetColumnWidth(nColIdx, 10);m_grid.SetItemText(nRowIdx, nColIdx++, _T("名称"));m_grid.SetColumnWidth(nColIdx, 50);m_grid.SetItemText(nRowIdx, nColIdx++, _T("描述"));m_grid.SetColumnWidth(nColIdx, 30);m_grid.SetItemText(nRowIdx, nColIdx++, _T("创建时间"));m_grid.SetFixedRowSelection(FALSE);     // 设置固定行不可选中m_grid.SetFixedColumnSelection(FALSE);  // 设置固定列不可选中m_grid.SetEditable(TRUE);				// 设置单元格可编辑m_grid.SetRowResize(FALSE);				// 设置行不可调整大小m_grid.SetColumnResize(TRUE);			// 设置列可调整大小m_grid.ExpandColumnsToFit(TRUE);		// 自动调整列宽,适合固定表格大小并希望所有列均匀分布的情况m_grid.SetListMode(TRUE);				// 启用列表模式m_grid.EnableSelection(TRUE);			// 启用选择m_grid.SetSingleRowSelection(TRUE);		// 自动整行高亮(限制为单行选择)m_grid.ExpandLastColumn();				// 最后一列填充网格FillRecipeLise();
}

2. 填充数据到表格

FillRecipeLise 函数实现从文件夹和文本文件读取数据,并填充到表格:

void CRecipeListDlg::FillRecipeLise()
{// 在设置行数和数据填充时,批量处理操作,避免逐行刷新表格。// 开头调用 SetRedraw(FALSE),结束后调用 SetRedraw(TRUE)。m_grid.SetRedraw(FALSE);// 动态行数检查:在清空旧数据时,确保不会越界。// 清除数据行,保留表头for (int i = m_grid.GetRowCount() - 1; i > 0; --i) {m_grid.DeleteRow(i);}// 1. 遍历文件夹下所有XML文件std::string strRecipePath = CToolUnits::getRecipePath();std::vector<CString> vecFile = CToolUnits::GetFileNamesInDirectory(strRecipePath.c_str(), _T(".xml"));// 2. 读取 RecipeList.txt 文件std::map<CString, std::pair<CString, CString>> recipeData; // {配方名, {描述, 创建时间}}std::ifstream inFile(strRecipePath + "\\RecipeList.txt");if (inFile.is_open()) {std::string line;while (std::getline(inFile, line)) {if (line.empty()) continue; // 跳过空行std::istringstream ss(line);std::string name, description, createTime;// CSV格式解析(逗号分隔)if (std::getline(ss, name, ',') &&std::getline(ss, description, ',') &&std::getline(ss, createTime)) {recipeData[CString(name.c_str())] = std::make_pair(CString(description.c_str()), CString(createTime.c_str()));}}inFile.close();}// 3. 更新表格数据int rowIdx = 1;m_grid.SetRowCount(static_cast<int>(vecFile.size()) + 1);for (const auto& fileName : vecFile) {// 从 RecipeList.txt 数据中查找对应的描述和创建时间CString description = _T("");CString createTime = _T("");auto it = recipeData.find(fileName);if (it != recipeData.end()) {description = it->second.first;  // 配方描述createTime = it->second.second;  // 创建时间}// 填充表格数据m_grid.SetItemText(rowIdx, 0, CString(std::to_string(rowIdx).c_str())); // No.m_grid.SetItemText(rowIdx, 1, fileName);								// 配方名称m_grid.SetItemText(rowIdx, 2, description);								// 配方描述m_grid.SetItemText(rowIdx, 3, createTime);								// 创建时间// 禁止编辑m_grid.SetItemState(rowIdx, 0, GVIS_READONLY);m_grid.SetItemState(rowIdx, 1, GVIS_READONLY);m_grid.SetItemState(rowIdx, 3, GVIS_READONLY);++rowIdx;}// 适合内容不固定的情况,列宽自适应内容长度更自然。m_grid.ExpandColumnsToFit(FALSE);  // 自动调整列宽m_grid.ExpandLastColumn();		   // 最后一列填充网格// 刷新网格控件m_grid.SetRedraw(TRUE);m_grid.Invalidate();m_grid.UpdateWindow();
}

七、总结

  1. Custom Control 提供了实现自定义控件的基础能力,适合高度个性化需求。
  2. CGridCtrl 是一个基于 Custom Control 的表格控件,提供了灵活的表格展示与管理功能。
  3. CGridCtrl 支持设置特殊单元格类型,如下拉框(CGridCellCombo)和复选框(CGridCellCheck)。

http://www.ppmy.cn/devtools/144065.html

相关文章

子域提取工具,子域名收集神器,支持多种数据源和枚举选项,域名发现工具,可以为任何目标枚举海量的有效子域名,安全侦察工具,利用证书透明原则监控部署的新子域

子域提取工具&#xff0c;子域名收集神器&#xff0c;支持多种数据源和枚举选项&#xff0c;域名发现工具&#xff0c;可以为任何目标枚举海量的有效子域名&#xff0c;安全侦察工具&#xff0c;利用证书透明原则监控部署的新子域。 需要对目标域名的子域进行深入分析&#xff…

Vue3 的 Teleport 是什么?在什么场景下会用到?

Teleport 的定义与工作原理 定义&#xff1a; Teleport 是 Vue3 新增的一个内置组件&#xff0c;它允许开发者将一个组件的模板内容渲染到指定的 DOM 节点中&#xff0c;而不是通常的组件挂载点。 工作原理&#xff1a; 通过 Teleport 组件的 to 属性&#xff0c;可以指定一…

自动外呼机器人如何与人工客服进行无缝切换?

自动外呼机器人如何与人工客服进行无缝切换&#xff1f; 作者&#xff1a;开源呼叫中心FreeIPCC 实现自动外呼机器人与人工客服之间的无缝切换是确保客户体验连续性和服务质量的关键。这不仅要求技术上的精密配合&#xff0c;还需要在流程设计、用户沟通和系统集成方面进行周…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证1)

本文开始学习基于JWT的身份认证基本用法&#xff0c;相比Cookie、Session等方式&#xff0c;JWT要复杂一些&#xff0c;除了注册认证服务之外&#xff0c;还需提供JWT Token的生成函数或生成类&#xff0c;以便在访问需授权的函数之前获取Token。参考文献1-7中大部分示例都是基…

Guava 库中的 `Multiset` 是一个允许元素重复的集合

Guava 库中的 Multiset 是一个允许元素重复的集合。它继承自 Collection 接口&#xff0c;提供了额外的方法来处理元素的计数。以下是一些使用 Guava Multiset 的基本代码示例&#xff1a; 引入 Guava 库 首先&#xff0c;确保你的项目中已经添加了 Guava 库的依赖。如果你使…

基于字节大模型的论文翻译(含免费源码)

基于字节大模型的论文翻译 源代码&#xff1a; &#x1f44f; star ✨ https://github.com/boots-coder/LLM-application 展示 项目简介 本项目是一个基于大语言模型&#xff08;Large Language Model, LLM&#xff09;的论文阅读与翻译辅助工具。它通过用户界面&#xff08…

基于Spring Boot的高校素拓分管理系统

一、系统背景与目的 随着高校教育的不断发展&#xff0c;素质拓展活动在大学生培养中扮演着越来越重要的角色。为了更好地管理和记录学生的素质拓展学分&#xff0c;提高管理效率&#xff0c;降低管理成本&#xff0c;开发一套基于Spring Boot的高校素拓分管理系统显得尤为重要…

CSSmodule的作用是什么

CSS Modules的作用主要体现在以下几个方面&#xff1a; 1. 解决全局样式污染问题 在传统的CSS管理方式中&#xff0c;样式定义通常是全局的&#xff0c;这很容易导致全局样式污染。当多个组件或页面共享同一个样式时&#xff0c;可能会出现样式冲突和覆盖的情况&#xff0c;从…