MFC提供DDX_Text資料交換與DDV_MaxChars/DDV_MinMaxInt基礎驗證,但:
DoDataExchange,函數膨脹難維護
void DDV_Regex(CDataExchange* pDX, CString& val, LPCTSTR pszRegex) {
if (pDX->m_bSaveAndValidate) {
CMRegex regex(pszRegex);
if (!regex.Match(val).Success()) {
pDX->Fail(); // 拋出例外,終止DDX
}
}
}
// 使用範例:郵遞區號 (##### 或 #####-####)
DDX_Text(pDX, IDC_ZIP, m_zip);
DDV_Regex(pDX, m_zip, _T("^\\d{5}(-\\d{4})?$"));
雖解決格式驗證,但仍遺留DDX/DDV架構缺陷。
BEGIN_REGEX_FORM(MyRegexForm)
RGXFIELD(IDC_ZIP, RGXF_REQUIRED, 0) // 必填
RGXFIELD(IDC_SSN, 0, 0) // 選填
RGXFIELD(IDC_PHONE, 0, 0)
RGXFIELD(IDC_TOKEN, 0, 0)
RGXFIELD(IDC_PRIME, RGXF_CALLBACK, 0) // 回調驗證
RGXFIELD(IDC_FAVCOL, 0, CMRegex::IgnoreCase) // 不分大小寫
END_REGEX_FORM()
// IDC_ZIP 對應資源字串
"Zip Code\n^\\d{5}(-\\d{4})?$\n[\\d-]\n##### or #####-####\n"
// 解析:
// 1. 欄位名稱: "Zip Code"
// 2. 驗證Regex: "^\\d{5}(-\\d{4})?$" (資源中需雙重轉義 \\\\d)
// 3. 合法字元Regex: "[\\d-]" (即時過濾用,空白表示不限制)
// 4. 工具提示: "##### or #####-####"
// 5. 錯誤訊息: (空白則自動產生 "Should be ##### or #####-####")
class CRegexForm {
struct FIELD_INFO {
int nCtrlID; // 控制項ID
DWORD dwFlags; // RGXF_REQUIRED, RGXF_CALLBACK...
DWORD dwRegexOpt; // CMRegex選項
CString strName; // 欄位名稱
CMRegex regexVal; // 驗證正則
CMRegex regexChar; // 字元過濾正則
CString strHint; // 工具提示
CString strErrMsg; // 自訂錯誤訊息
};
CArray m_fields;
CToolTipCtrl m_tooltip;
CEdit m_feedbackWnd; // 錯誤回饋視窗
// ...
};
EN_UPDATE中以regexChar驗證新字元,非法則MessageBeep並撤銷WM_SETFOCUS顯示strHint工具提示OnOK中遍歷所有欄位,regexVal.Match()失敗收集錯誤m_feedbackWnd顯示詳細錯誤針對正則無法表達的邏輯 (如質數驗證):
// 巨集標記 RGXF_CALLBACK
// 衍生類別覆寫虛擬函數
virtual BOOL OnRegexCallback(int nCtrlID, const CString& strValue, CString& strErrMsg) {
if (nCtrlID == IDC_PRIME) {
long n = _ttol(strValue);
if (n < 2 || !IsPrime(n)) {
strErrMsg = _T("必須為質數");
return FALSE;
}
}
return TRUE;
}
所有使用者可見字串 (名稱、提示、錯誤訊息) 集中在資源檔,翻譯時無需修改程式碼,甚至可執行期切換語言資源 DLL。
CRegexForm將MFC對話框驗證從「程序式硬編碼」昇級為「宣告式表格驅動」,結合正則表達式表達力與豐富互動體驗 (即時過濾、工具提示、彙總錯誤、回饋視窗),並解決在地化難題。這是「拒絕程序化代碼、一個表格勝過千行代碼」原則的最佳實踐。