程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> MFC中用正則表達式進行有效性驗證

MFC中用正則表達式進行有效性驗證

編輯:關於C語言

  正則表達式最實用的一個地方是驗證用戶輸入。它可以輕松驗證郵編、電話號碼、信用卡號碼——以及現實世界中各種類型的信息。一個正則表達式可以替換成打甚至上百行過程代碼。UNIX 和 Web 編程語言如 Perl從一開始就有正則表達式,但在 Windows 世界或MFC,從來都是使用第三方庫,一直到 .NET 框架才結束這個局面。因此現在 .NET 提供一個完整的正則表達式庫,為什麼不在MFC應用程序中使用它呢?利用 RegexWrap 庫,你甚至都不需要托管擴展或 /clr。

   MFC 已經具備一種稱為“對話框數據交換”(Dialog Data Exchange,即 DDX)以及“對話框數據驗證”(Dialog Data Validation,即 DDV)的機制來驗證對話框輸入。從技術上講,DDX 只是在屏幕和你的對話框對象之間傳輸數據,而 DDV 才驗證數據。當你從對話框的 OnOK 處理例程中調用 UpdateData 時 DDX 才開始工作。

// user pressed OK:
void CMyDialog::OnOK() {
  UpdateData(TRUE); // 獲得對話框數據
  ...
}

   UpdateData 是一個虛擬 CWnd 函數,你可以在自己的對話框中重寫這個函數。其布爾型(Boolean)參數告知是將信息拷貝到屏幕還是相反從屏幕拷貝信息。(你可以在 OnInitDialog 中調用 UpdateData(FALSE)以便初始化對話框)。默認的 CWnd 實現創建一個 CDataExchange 對象並將它傳遞到另一個虛擬函數,DoDataExchange,你得重寫這個函數去調用專門的 DDX 函數來為單獨的數據成員傳遞數據:

void CMyDialog::DoDataExchange(CDataExchange* pDX) {
  CDialog::DoDataExchange(pDX);
  DDX_Text(pDX, IDC_NAME, m_name);
  DDX_Text(pDX, IDC_AGE, m_age);
  ...
  // etc.
}

   這裡 IDC_NAME 和 IDC_AGE 是編輯控制的 IDs,m_name 和 m_age 分別是 CString 和 int 數據成員。DDX_Text 將用戶輸入的 Name 和 Age 拷貝到 m_name 和 m_age(用一個重載順便將 Age 轉變成 int)。DDX 函數知道走哪條路,因為當從屏幕拷貝到對話框時,CDataExchange::m_bSaveAndValidate 為 TRUE,反之則為 FALSE。MFC 為各種數據和控制類型加載 DDX 函數。例如,DDX_Text 至少有一些重載函數用來將輸入文本拷貝和轉換成不同的類型,如 CString、int、double、COleCurrency 等等。DDX_Check 用來將復選框的狀態轉換成整型值,DDX_Radio 則對單選按鈕做同樣的事情。

   DDX 函數傳輸數據;DDV 函數則驗證它。例如,為了限制用戶名稱為 35個字符,你可以這樣做:

// in CMyDialog::DoDataExchange
DDX_Text(pDX, IDC_NAME, m_sName); // 獲得/設置值
DDV_MaxChars(pDX, m_sName, 35); // 驗證

   為了限定你的用戶年齡為 1-120之間的一個整數,你可以這樣寫:

// m_age is int
DDX_Text(pDX, IDC_AGE, m_age);
DDV_MinMaxInt(pDX, m_age, 1, 120);

   雖然 DDX 工作表現得很好,DDV 是不免有點老土。MFC 在有效性驗證方面所能做到的很有限。你可以在文本域中限制數字字符,不同類型的最小/最大約束。最小/最大是不錯,但如果你想驗證郵編或電話號碼怎麼辦?MFC 對此無能為力。你不得不編寫自己的 DDV 函數。當我第一次用正則表達式實現有效性驗證時,我只要寫一個函數即可,就像這樣:
 
void DDV_Regex(CDataExchange* pDX, CString& val,
LPCTSTR pszRegex)
{
  if (pDX->m_bSaveAndValidate) {
   CMRegex r(pszRegex);
   if (!r.Match(val).Success()) {
    pDX->Fail(); // throws exception
   }
  }
}

   這使你很容易象下面這樣用正則表達式驗證輸入:

// in CMyDialog::DoDataExchange
DDX_Text(pDX, IDC_ZIP, m_zip);
DDV_Regex(pDX, m_zip,_T("^\d{5}(-\d{4})?$"));

   好酷啊,僅用四行代碼就搞掂。(當然,那要假設你有 RegexWrap——否則你得使用托管擴展直接調用框架 Regex 類。)DDV_Regex 在 MFC 的 DDX/DDV 方案中工作表現很完美,但是當我開始添加更多的域時,我馬上發現一些 DDX/DDV 的主要缺點,其一,如果域輸入無效,則每個 DDV 函數都顯示一個出錯消息框並丟出異常,那麼要是有五個無效域,用戶就會看到五個消息框——真實糟透了!此外,在對 DDV 的調用中,我不想將正則表達式寫死在代碼中。但我拒絕 DDX/DDV 的主要理由是它太程序化。為了驗證新的域,你不得不添加另外的數據成員以及在 DoDataExchange 加更多的代碼,不久這個函數便膨脹臃腫,就像下面這樣:

DDX_Text(pDX, IDC_FOO,...);
DDV_Mumble(pDX, ...)
DDX_Text(pDX, IDC_BAR,...);
DDV_Bletch(...)
... // etc for 14 lines

   為什麼我非得要墨守成規編寫過程指令來描述固有的驗證規則呢?我的五條編程最高准則之一是:拒斥程序化代碼。另一個是:一個表格勝過一千行代碼。你肯定猜到我要干什麼了。最終,我編寫自己的對話框驗證系統,一個基於規則的、表格驅動的驗證系統。它依靠在 DDX 的最上層,但廢掉了 DDV 並有好得多的用戶接口。當然,它還易於使用,借助正則表達式進行驗證。所有細節都封裝在一個類中,CRegexForm,你可以在任何 MFC 對話框中使用這個類。


Figure 1 TestForm 裡的工具提示

 與往常一樣,我編寫了一個測試程序來示范它的工作原理。初看起來,TestForm 有點像再平常不過的基於 MFC 的對話框程序。它的主對話框中有幾個編輯框——郵編、SSN(社會保險號),電話號碼等。但當你通過對 TestForm 的測試,你會很快認識到它蘊含著許多玄機。如果你用tab鍵在輸入域間移動,TestForm 會顯示一個工具提示,它描述在這個輸入域能輸入什麼(如圖 Figure 1)。如果你敲入了一個非法字符——例如,在電話號碼輸入域敲入一個字符——TestForm 將拒絕接受該字符並發出蜂鳴聲。當你按下確認鍵或OK鍵,你會得到一個描述所有無效輸入域的出錯信息框,如圖 Figure 2 所示。所有的錯誤都顯示在一個消息框中,而不是每個錯誤一個消息框。接著當用戶tab到其中一個無效輸入域時,TestForm 會再次在對話框本身的一個應用程序提供的“反饋”窗口內顯示出錯信息(如 Figure 3),因此用戶不必記住錯誤信息所描述的內容,當他們更正每個無效輸入域時,窗體會提醒他們。如果只有一個輸入域無效,TestForm 會放棄消息框而直接在“反饋”窗口顯示錯誤信息。


Figure 2 集中顯示出錯的輸入域
   所有這些神奇的特性都是由 CRegexForm 自己實現的。你只需使用它即可,使用方法相當直白,首先你得定義一個自己窗體。下面是 TestForm 的窗體內容,這些定義位於 MainDlg.cpp:

// form/field map
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()

   這個宏定義了一個靜態表格,表格描述每個編輯控制域。大多數情況下你只需要控制 ID,還要有地方放標志和 RegexOptions。例如,在 TestForm 中,郵政編碼是必輸域(RGXF_REQUIRED),質數(Prime Number)輸入域使用回調(稍後會詳細討論),最喜愛的專欄作家(IDC_FAVCOL)指定 CMRegex::IgnoreCase,它使得大小寫 不敏感。


Figure 3 Pietrek? I Think Not!
   看著表格你可能想知道正則表達式在哪。回答是:在資源文件中。對於每個域/控制ID,CRegexForm 都期望有一個具有相同ID的資源串。資源串由五個子串組成,子串之間用新行符( )分隔。一般格式為:
“Name Regex LegalChars Hint ErrMsg”。以下是 IDC_ZIP 所用的串:

 "Zip Code ^\d{5}(-\d{4})?$ [\d-] ##### or #####-####"
   第一個子串“Zip Code”是域名。第二個“^d{5}(-d{4})?$”,是用於驗證郵編的正則表達式。(在資源串中必須敲入兩個反斜線,目的是轉義正則表達式中反斜線)。第三個子串是另外一個正則表達式,用來描述合法字符。對於郵編來說,即是“[d-]”,意思是允許數字和連字符(hyphen)。如果輸入域無字符限制,你可以通過敲入兩個連續的新行符(“ ”意思是空子串)省略 LegalChars 檢查。第四個子串全部為工具提示串。最後你可以提供第五個子串,如果該輸入域無效則顯示錯誤信息。對於郵編來說,它沒有錯誤信息,所以 CRegexForm 產生一個默認的信息,形式為“Should be xxx”,xxx 被工具提示替代。“Should be”本身即是另一個資源串(稍後還要說到)。這些子串中,只有第一個域是必輸域。

   為什麼用資源串來保存所有信息,而不直接在域映射中編碼處理呢?首先,將它放在映射中使得代碼很笨拙。把這些亂七八糟的字符串放在不顯眼的地方有利於代碼更整潔。此外宏無法處理可選參數,根據你所用參數的多少,你需要多個宏,如:RGXFIELD3、RGXFIELD4 和 RGXFIELD5。這樣不是太笨拙了嘛?使用資源串真正的好處在於容易本地化

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved