One of the most practical aspects of a regular expression is to verify user input. It can easily verify zip codes, phone numbers, credit card numbers-and various types of information in the real world. A regular expression can be replaced with hundreds of lines of Process Code. UNIX and Web programming languages such as Perl have regular expressions from the very beginning, but in the Windows world or MFC, they have always used third-party libraries until the. NET Framework ends. So now. NET provides a complete library of regular expressions. Why not use it in the MFC application? With the RegexWrap library, you do not even need to host extensions or/clr.
MFC already has a mechanism called "Dialog Data Exchange" (DDX) and "Dialog Data Validation" (DDV) to verify Dialog box input. Technically speaking, DDX only transfers data between the screen and your dialog box object, while DDV verifies the data. When you call UpdateData from the OnOK processing routine in the dialog box, DDX starts to work.
// User pressed OK:
Void CMyDialog: OnOK (){
UpdateData (TRUE); // obtain the data in the dialog box
...
}
UpdateData is a virtual CWnd function. You can rewrite this function in your own dialog box. The Boolean parameter indicates whether to copy information to the screen or vice versa. (You can call UpdateData (FALSE) in OnInitDialog to initialize the dialog box ). The default CWnd implementation creates a CDataExchange object and passes it to another virtual function, DoDataExchange. You have to rewrite this function to call a dedicated DDX function to transmit data for individual data members:
void CMyDialog::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_NAME, m_name);
DDX_Text(pDX, IDC_AGE, m_age);
...
// etc.
}
Here IDC_NAME and IDC_AGE are the IDs controlled by editing, and m_name and m_age are the CString and int data members respectively. DDX_Text copies the Name and Age entered by the user to m_name and m_age (convert the Age to int by using an overload ). The DDX function knows the path, because when the screen is copied to the dialog box, CDataExchange: m_bSaveAndValidate is TRUE, and otherwise FALSE. MFC loads DDX functions for various data and control types. For example, DDX_Text has at least some overload functions to copy and convert the input text to different types, such as CString, int, double, and COleCurrency. DDX_Check is used to convert the status of the check box to an integer value. DDX_Radio performs the same operation on the single-choice button.
The DDX function transfers data. The DDV function verifies the data. For example, to restrict the user name to 35 characters, you can do this:
// In CMyDialog: DoDataExchange
DDX_Text (pDX, IDC_NAME, m_sName); // obtain/set the value
DDV_MaxChars (pDX, m_sName, 35); // Verification
To limit your user's age to an integer between 1 and, you can write as follows:
// m_age is int
DDX_Text(pDX, IDC_AGE, m_age);
DDV_MinMaxInt(pDX, m_age, 1, 120);
Although DDX is doing well, DDV is a little boring. The effectiveness of MFC is very limited. You can limit the number characters in the text field. Different types of minimum/maximum constraints are allowed. The minimum/maximum value is good, but what if you want to verify the zip code or phone number? MFC is powerless. You have to write your own DDV function. When I use a regular expression to verify the validity for the first time, I only need to write a function like this:
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
}
}
}
This makes it easy for you to use a regular expression to verify the input as follows:
// in CMyDialog::DoDataExchange
DDX_Text(pDX, IDC_ZIP, m_zip);
DDV_Regex(pDX, m_zip,_T("^\d{5}(-\d{4})?$"));
Cool, just use four lines of code to get started. (Of course, assume you have RegexWrap -- Otherwise you have to use managed extensions to directly call the framework Regex class .) DDV_Regex works perfectly in the DDX/DDV solution of MFC. But when I started to add more domains, I immediately found some primary disadvantages of DDX/DDV, if the field input is invalid, an error message box is displayed for each DDV function and an exception is thrown. If there are five invalid fields, the user will see five message boxes, which are really bad! In addition, in the call to DDV, I do not want to write the regular expression to the code. But the main reason why I refuse DDX/DDV is that it is too procedural. To verify the new domain, you have to add additional data members and add more code to DoDataExchange. Soon this function will expand and bloat, as shown below:
DDX_Text(pDX, IDC_FOO,...);
DDV_Mumble(pDX, ...)
DDX_Text(pDX, IDC_BAR,...);
DDV_Bletch(...)
... // etc for 14 lines
Why do I have to write process instructions to describe inherent verification rules? One of my five top five programming principles is: rejecting Procedural Code. The other is: a table is better than one thousand lines of code. You must have guessed what I'm going to do. Finally, I wrote my own dialog box verification system, a rule-based, table-driven verification system. It relies on the top layer of DDX, But it discards DDV and has a much better user interface. Of course, it is easy to use and is verified using regular expressions. All the details are encapsulated in a class, CRegexForm. You can use this class in any MFC dialog box.
Tooltip in Figure 1 TestForm
As usual, I wrote a test program to demonstrate how it works. At first glance, TestForm is a bit like an ordinary MFC-based dialog box program. The main dialog box contains several editing boxes: zip code, SSN (Social Insurance number), and telephone number. But when you pass the TestForm test, you will soon realize that it contains many xuanjicang. If you use the tab key to move between input fields, TestForm displays a tooltip that describes what can be entered in this input field (Figure 1 ). If you type an invalid character, for example, if you type a character in the phone number field, TestForm rejects the character and makes a beep. When you press the confirm or OK key, you will get an error message box describing all invalid input fields, as shown in Figure 2. All errors are displayed in one message box, instead of one message box for each error. Then, when the user tabs to an invalid input field, TestForm displays the error information (such as Figure 3) in the "feedback" window provided by an application in the dialog box itself ), therefore, users do not have to remember the content described in the error message. When they correct each invalid input field, the form will remind them. If only one input field is invalid, TestForm will discard the message box and display the error message directly in the "feedback" window.
In Figure 2, error input fields are displayed.
All these magical features are implemented by CRegexForm itself. You only need to use it. The method is straightforward. First, you need to define your own form. The following is the content of the form of TestForm. These definitions are located in 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()
This macro defines a static table that describes each editing control field. In most cases, you only need to control the ID and place the flag and RegexOptions. For example, in TestForm, the zip code is a mandatory field (RGXF_REQUIRED), the Prime Number input field uses callback (which will be discussed in detail later), and the favorite columnist (IDC_FAVCOL) specify CMRegex: IgnoreCase, which makes the case insensitive.
Figure 3 Pietrek? I Think Not!
Looking at the table, you may want to know where the regular expression is. The answer is: in the resource file. For each domain/Control ID, CRegexForm expects a resource string with the same ID. A resource string consists of five substrings, which are separated by a new line character. The general format is:
"NameRegexLegalCharsHintErrMsg ". The following are the strings used by IDC_ZIP:
"Zip Code^\d{5}(-\d{4})?$[\d-]##### or #####-####"
The first substring "Zip Code" is the domain name. The second "^ d {5} (-d {4 })? $ "Is a regular expression used to verify the zip code. (Two backslashes must be input to the resource string to escape the backslashes in the regular expression ). The third substring is another regular expression used to describe valid characters. For the zip code, it is "[d-]", which means to allow numbers and hyphens (hyphen ). If the input field has no character restrictions, You can omit the LegalChars check by typing two consecutive newlines ("" means an empty substring. The fourth substring is a tooltip string. Finally, you can provide the Fifth substring. If the input field is invalid, an error message is displayed. There is no error message for the zip code. Therefore, CRegexForm generates a default message in the form of "shocould be xxx". xxx is replaced by a tooltip. "Shocould be" itself is another resource string (to be mentioned later ). Among these substrings, only the first domain is required.
Why do I use a resource string to store all the information without coding in the domain ing? First, put it in the ing to make the code very clumsy. Placing these messy strings in an inconspicuous place helps the code to be more tidy. In addition, macros cannot process optional parameters. Depending on the number of parameters you use, you need multiple macros, such as RGXFIELD3, RGXFIELD4, and RGXFIELD5. Isn't it too clumsy? The real advantage of using resource strings is that they are easy to localize.