在用户界面开发中,输入框是最基础的控件之一。然而,在许多场景下,我们希望限制用户的输入格式,例如只能输入数字、小数、字母等。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();
        }
    }
}配置校验规则
实现 SetRegexType 和 GetCurrentRegex 函数。
            
            
              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 可以轻松适应多种场景,既满足通用需求,也能满足复杂的自定义需求。