MFC 自定义编辑框:打造灵活的数据输入控件

在用户界面开发中,输入框是最基础的控件之一。然而,在许多场景下,我们希望限制用户的输入格式,例如只能输入数字、小数、字母等。MFC 提供了强大的自定义控件机制,结合正则表达式,可以实现灵活的数据输入校验。本文将介绍如何通过自定义编辑框与正则表达式的结合,打造灵活高效的输入控件。

什么是正则表达式?

正则表达式(Regular Expression, regex) 是一种描述字符模式的语言。它用于字符串的查找、匹配和替换操作。通过正则表达式,可以轻松定义复杂的文本格式校验规则。

常用的正则表达式语法

符号 说明
. 匹配任意字符(除换行符外)
* 匹配前一个字符 0 次或多次
+ 匹配前一个字符 1 次或多次
? 匹配前一个字符 0 次或 1 次
[abc] 匹配方括号中的任意一个字符
[^abc] 匹配不在方括号中的字符
\d 匹配数字(等价于 [0-9]
\w 匹配字母、数字或下划线
\s 匹配空白字符(空格、制表符等)
^ 匹配字符串开头
$ 匹配字符串结尾
() 表示捕获分组
` `

示例正则表达式

表达式 描述
^\d+$ 只包含数字,且至少一个字符
^\d{3}$ 必须是 3 位数字
^[a-zA-Z]+$ 只包含大小写字母
^\w+@\w+\.\w+$ 简单的邮箱格式验证
^\d+(\.\d{1,2})?$ 数字,可以有 1-2 位小数

自定义编辑框与正则表达式结合

在 MFC 中,自定义控件可以通过派生子类实现。我们设计了一个 CRegexEdit 类,用于结合正则表达式进行输入校验。它支持以下功能:

  • 限制输入的格式,例如只能输入数字、字母、小数等。
  • 提供自定义比较函数,可以实现范围校验等功能。
  • 当输入不合法时,支持触发回调函数,提示用户。

以下是 CRegexEdit 的实现核心代码。

实现编辑框的校验逻辑

OnChar 中实现字符输入校验逻辑。

cpp 复制代码
void CRegexEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // 处理删除键和退格键
    if (nChar == VK_BACK || nChar == VK_DELETE) {
        CEdit::OnChar(nChar, nRepCnt, nFlags);
        return;
    }

    CString strCurrent;
    GetWindowText(strCurrent);  // 获取当前文本

    // 获取光标位置
    int nStartChar, nEndChar;
    GetSel(nStartChar, nEndChar);

    std::string strText(CT2A(strCurrent.GetBuffer()));
    strText.insert(strText.begin() + nStartChar, (char)nChar);

    // 校验规则:正则表达式 + 范围校验
    bool bValid = m_customComparator ? m_customComparator(strText) : IsValueInRange(strText);

    if (std::regex_match(strText, GetCurrentRegex()) && bValid) {
        CEdit::OnChar(nChar, nRepCnt, nFlags);
    }
    else {
        // 输入错误处理
        if (m_invalidInputCallback) {
            m_invalidInputCallback();
        }
    }
}

配置校验规则

实现 SetRegexTypeGetCurrentRegex 函数。

cpp 复制代码
void CRegexEdit::SetRegexType(RegexType enType)
{
    m_enRegexType = enType;
}

std::regex CRegexEdit::GetCurrentRegex() const
{
    switch (m_enRegexType)
    {
    case RegexType::Alphanumeric:
        return std::regex("^[a-zA-Z0-9]*$");        // 字母和数字
    case RegexType::Letters:
        return std::regex("^[a-zA-Z]*$");           // 只允许字母
    case RegexType::Digits:
        return std::regex("^\\d*$");                // 只允许数字
    case RegexType::Decimal:
        return std::regex("^[-+]?\\d+(\\.\\d+)?$"); // 允许小数和整数
    case RegexType::Custom:
        return std::regex(m_strCustomRegex);        // 自定义正则
    default:
        return std::regex(".*");                    // 默认允许任何内容
    }
}

支持查找和替换

支持用户查找和替换编辑框中的内容。

cpp 复制代码
bool CRegexEdit::FindMatch(const std::string& pattern, std::string& foundText)
{
    CString currentText;
    GetWindowText(currentText);

    std::string text(CT2A(currentText.GetString()));
    std::regex regexPattern(pattern);
    std::smatch match;

    if (std::regex_search(text, match, regexPattern)) {
        foundText = match.str();
        return true;
    }
    return false;
}

void CRegexEdit::ReplaceMatch(const std::string& pattern, const std::string& replacement)
{
    CString currentText;
    GetWindowText(currentText);

    std::string text(CT2A(currentText.GetString()));
    std::regex regexPattern(pattern);
    std::string result = std::regex_replace(text, regexPattern, replacement);

    SetWindowText(CString(result.c_str()));
}

类声明

cpp 复制代码
#if !defined(AFX_REGEXEDIT_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
#define AFX_REGEXEDIT_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include <afxwin.h>
#include <regex>
#include <functional>
#include <limits>

// 枚举类型:正则表达式类型
enum class RegexType
{
    Alphanumeric,   // 允许字母和数字
    Letters,        // 只允许字母
    Digits,         // 只允许数字
    Decimal,        // 允许小数和整数
    Custom          // 自定义正则
};

class CRegexEdit : public CEdit
{
    DECLARE_DYNAMIC(CRegexEdit)

public:
    // 构造与析构
    CRegexEdit();
    virtual ~CRegexEdit();

    // 设置正则类型
    void SetRegexType(RegexType enType);

    // 设置自定义正则表达式
    void SetCustomRegex(const std::string& strCustomRegex);

    // 设置数值范围(整数或浮点)
    void SetValueRange(long double dMinValue, long double dMaxValue);

    // 设置自定义比较函数
    void SetCustomComparator(std::function<bool(const std::string&)> comparator);

	// 设置输入不合法函数
    void SetInvalidInputCallback(std::function<void()> callback);

    // 查找匹配内容
    bool FindMatch(const std::string& pattern, std::string& foundText);

    // 替换匹配内容
    void ReplaceMatch(const std::string& pattern, const std::string& replacement);

protected:
    // 根据枚举值返回对应的正则表达式
    std::regex GetCurrentRegex() const;

    // 校验输入是否在指定范围内
    bool IsValueInRange(const std::string& strText);

protected:
    RegexType m_enRegexType;        // 当前选中的正则类型
    std::string m_strCustomRegex;   // 自定义正则表达式
	long double m_dMinValue;        // 最小值
	long double m_dMaxValue;		// 最大值

	std::function<bool(const std::string&)> m_customComparator; // 自定义比较函数
    std::function<void()> m_invalidInputCallback;               // 不合法输入的回调函数

protected:
    void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    DECLARE_MESSAGE_MAP()
};

#endif // !defined(AFX_REGEXEDIT_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)

类实现

cpp 复制代码
#include "stdafx.h"
#include "RegexEdit.h"
#include <stdexcept>

IMPLEMENT_DYNAMIC(CRegexEdit, CEdit)

CRegexEdit::CRegexEdit()
{
    m_enRegexType = RegexType::Alphanumeric;
	m_dMinValue = LDBL_MIN;
	m_dMaxValue = LDBL_MAX;
}

CRegexEdit::~CRegexEdit()
{
}

void CRegexEdit::SetRegexType(RegexType enType)
{
    m_enRegexType = enType;
}

void CRegexEdit::SetCustomRegex(const std::string& strCustomRegex)
{
    m_strCustomRegex = strCustomRegex;
}

void CRegexEdit::SetValueRange(long double dMinValue, long double dMaxValue)
{
    m_dMinValue = dMinValue;
    m_dMaxValue = dMaxValue;
}

void CRegexEdit::SetCustomComparator(std::function<bool(const std::string&)> comparator)
{
	m_customComparator = comparator;
}

void CRegexEdit::SetInvalidInputCallback(std::function<void()> callback)
{
    m_invalidInputCallback = callback;
}

bool CRegexEdit::FindMatch(const std::string& pattern, std::string& foundText)
{
    CString currentText;
    GetWindowText(currentText);

    std::string text(CT2A(currentText.GetString()));
    std::regex regexPattern(pattern);
    std::smatch match;

    if (std::regex_search(text, match, regexPattern)) {
        foundText = match.str();
        return true;
    }
    return false;
}

void CRegexEdit::ReplaceMatch(const std::string& pattern, const std::string& replacement)
{
    CString currentText;
    GetWindowText(currentText);

    std::string text(CT2A(currentText.GetString()));
    std::regex regexPattern(pattern);
    std::string result = std::regex_replace(text, regexPattern, replacement);

    SetWindowText(CString(result.c_str()));
}

std::regex CRegexEdit::GetCurrentRegex() const
{
    switch (m_enRegexType)
    {
    case RegexType::Alphanumeric:
        return std::regex("^[a-zA-Z0-9]*$");        // 字母和数字
    case RegexType::Letters:
        return std::regex("^[a-zA-Z]*$");           // 只允许字母
    case RegexType::Digits:
        return std::regex("^\\d*$");                // 只允许数字
    case RegexType::Decimal:
        return std::regex("^[-+]?\\d+(\\.\\d+)?$"); // 允许小数和整数
    case RegexType::Custom:
        return std::regex(m_strCustomRegex);        // 自定义正则
    default:
        return std::regex(".*");                    // 默认允许输入任何内容
    }
}

bool CRegexEdit::IsValueInRange(const std::string& strText)
{
    try {
        if (m_enRegexType == RegexType::Digits || m_enRegexType == RegexType::Decimal) {
            if (strText.find('.') == std::string::npos) {
                int nValue = std::stoi(strText);
                return nValue >= static_cast<int>(m_dMinValue) && nValue <= static_cast<int>(m_dMaxValue);
            }
            else {
                double dValue = std::stod(strText);
                return dValue >= m_dMinValue && dValue <= m_dMaxValue;
            }
        }
    }
    catch (const std::invalid_argument&) {
        return false;
    }

    return true;
}

BEGIN_MESSAGE_MAP(CRegexEdit, CEdit)
    ON_WM_CHAR()
END_MESSAGE_MAP()

void CRegexEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // 处理删除键和退格键
    if (nChar == VK_BACK || nChar == VK_DELETE) {
        CEdit::OnChar(nChar, nRepCnt, nFlags);
        return;
    }

    CString strCurrent;
    GetWindowText(strCurrent);

    // 获取光标当前位置
    int nStartChar, nEndChar;
    GetSel(nStartChar, nEndChar); // 获取当前选区的起始和结束位置

    std::string strText(CT2A(strCurrent.GetBuffer()));
    strText.insert(strText.begin() + nStartChar, (char)nChar);

    bool bValid = m_customComparator ? m_customComparator(strText) : IsValueInRange(strText);
    if (std::regex_match(strText, GetCurrentRegex()) && bValid) {
        CEdit::OnChar(nChar, nRepCnt, nFlags);
    }
    else {
        if (m_invalidInputCallback) {
            m_invalidInputCallback();
        }
    }
}

如何使用自定义编辑框

定义编辑框控件

在对话框类中,添加一个自定义的编辑框控件:

cpp 复制代码
CRegexEdit m_editNumeric;

初始化控件

在对话框的 OnInitDialog 方法中初始化控件:

cpp 复制代码
m_editNumeric.SubclassDlgItem(IDC_EDIT_NUMERIC, this);
m_editNumeric.SetRegexType(RegexType::Decimal);  // 设置为输入数字
m_editNumeric.SetValueRange(0.0, 100.0);         // 设置数值范围为 0.0 到 100.0
m_editNumeric.SetInvalidInputCallback([]() {
    AfxMessageBox(_T("输入不合法,请检查输入内容!"));
});

查找和替换操作

cpp 复制代码
void CYourDialog::OnFindAndReplace()
{
    std::string foundText;

    // 查找匹配的电话号码
    if (m_editBox.FindMatch(R"(\d{3}-\d{3}-\d{4})", foundText)) {
        AfxMessageBox(CString("Found: ") + CString(foundText.c_str()));
    }
    else {
        AfxMessageBox(_T("No match found."));
    }

    // 替换所有数字为 "[REDACTED]"
    m_editBox.ReplaceMatch(R"(\d+)", "[REDACTED]");
}

常见替换场景

隐藏敏感信息

  • 匹配模式:R"(\d{3}-\d{3}-\d{4})"(电话号码)
  • 替换为:"***-***-****"
cpp 复制代码
m_editBox.ReplaceMatch(R"(\d{3}-\d{3}-\d{4})", "***-***-****");

标准化日期格式

  • 匹配模式:R"(\d{2})/(\d{2})/(\d{4})"(如 "12/31/2023")
  • 替换为:"$3-$1-$2"(如 "2023-12-31")
cpp 复制代码
m_editBox.ReplaceMatch(R"((\d{2})/(\d{2})/(\d{4}))", "$3-$1-$2");

提取并标记关键词

  • 匹配模式:R"(error|warning|critical)"(关键词)
  • 替换为:"<b>$1</b>"(加粗显示)
cpp 复制代码
m_editBox.ReplaceMatch(R"(error|warning|critical)", "<b>$1</b>");

常见正则表达式

以下是一些常用的正则表达式规则,可以直接在 SetCustomRegex 中使用:

功能 正则表达式 描述
整数 ^-?\d+$ 可正可负的整数
浮点数(小数) ^-?\d+(\.\d+)?$ 可正可负的小数和整数
邮箱地址 ^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$ 邮箱格式校验
日期(yyyy-mm-dd) ^\d{4}-\d{2}-\d{2}$ 日期格式校验
电话号码(11 位数字) ^1[3-9]\d{9}$ 简单的中国手机号码格式校验

常用的编辑框消息宏

  • 用户输入相关
    • ON_WM_CHAR
    • ON_WM_KEYDOWN
    • ON_WM_KEYUP
  • 焦点相关
    • ON_WM_SETFOCUS
    • ON_WM_KILLFOCUS
  • 内容变化相关
    • ON_EN_CHANGE
    • ON_EN_UPDATE
  • 鼠标相关
    • ON_WM_LBUTTONDOWN
    • ON_WM_LBUTTONUP
    • ON_WM_MOUSEMOVE
    • ON_WM_RBUTTONDOWN
    • ON_WM_RBUTTONUP
    • ON_WM_MOUSEHOVER
  • 外观绘制相关
    • ON_WM_CTLCOLOR
    • ON_WM_NCPAINT
    • ON_WM_PAINT
消息宏 对应的函数 说明
ON_WM_CHAR OnChar 处理字符输入事件(如按键字符)。
ON_WM_KEYDOWN OnKeyDown 处理键盘按下事件(如功能键、方向键)。
ON_WM_KEYUP OnKeyUp 处理键盘释放事件。
ON_WM_SETFOCUS OnSetFocus 编辑框获得输入焦点时触发。
ON_WM_KILLFOCUS OnKillFocus 编辑框失去输入焦点时触发。
ON_EN_CHANGE OnChange 编辑框内容发生变化时触发。
ON_EN_UPDATE OnUpdate 编辑框内容即将更新时触发。
ON_WM_LBUTTONDOWN OnLButtonDown 鼠标左键在编辑框内按下时触发。
ON_WM_LBUTTONUP OnLButtonUp 鼠标左键在编辑框内释放时触发。
ON_WM_MOUSEMOVE OnMouseMove 鼠标在编辑框内移动时触发。
ON_WM_RBUTTONDOWN OnRButtonDown 鼠标右键在编辑框内按下时触发。
ON_WM_RBUTTONUP OnRButtonUp 鼠标右键在编辑框内释放时触发。
ON_WM_CTLCOLOR OnCtlColor 更改编辑框的背景颜色或文本颜色。
ON_WM_MOUSEHOVER OnMouseHover 鼠标悬停在编辑框时触发。
ON_WM_NCPAINT OnNcPaint 非客户区(如边框)需要绘制时触发。
ON_WM_PAINT OnPaint 编辑框需要重绘时触发(如更新内容时)。

总结

通过自定义 CRegexEdit,我们可以将正则表达式强大的字符串校验能力与 MFC 的编辑框控件结合起来,实现灵活的输入校验功能。借助枚举类型的封装和回调函数的支持,CRegexEdit 可以轻松适应多种场景,既满足通用需求,也能满足复杂的自定义需求。

相关推荐
卜及中几秒前
【数据结构】B树家族解析:B树、B+树与B*树的理论与B树插入实现(C++)
开发语言·数据结构·c++·b树
PluviophileDD2 小时前
【笔记】C语言转C++
c语言·c++
LaoWaiHang3 小时前
MFC案例:基于对话框的简易阅读器
mfc
逆袭之路6663 小时前
浅谈右值引用 移动语义 完美转发 std::move std::forward,窥探模板元编程的一角
c++
安於宿命3 小时前
【Linux】文件系统
linux·服务器·c++
oioihoii3 小时前
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
开发语言·c++
OTWOL3 小时前
预处理基础指南
开发语言·数据结构·c++·算法
老猿讲编程4 小时前
【零成本抽象】汽车嵌入式软件开发中零成本抽象的功能安全考量与应对策略
c++·安全·汽车·零成本抽象
ZPILOTE4 小时前
日志基础示例python和c++
c++·python·日志·log·logger·glog