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