DDX (Dynamic Data Exchange) and DDV (dynamic data verification) Seem like a widget and
A connection is established between a member variable to automatically transfer data between the control and the variable. but this is just an illusion. it actually works like this: When you use classwizard to connect a variable to the control (through the member variables tab), it creates an entry in data ing. in fact, add an entry function to the dodataexchange function in the dialog box (the dodataexchange function is a function generated and maintained by Class Wizard ). when you call updatedata (false), MFC calls the dodataexchange function. The implementation code of Class Wizard in dodataexchange will copy the data from the variable to the corresponding control. if updatedata (true) is called, MFC copies the data back to the variable (and may perform data verification at the same time)
It should be noted that cdialog often calls updatedata (false) in the oninitdialog function, so that when the dialog box is displayed, your member variables will appear mysteriously in the dialog box. the onok function also calls updatedata, but the parameter is true. in this way, the modal dialog box looks like you have processed yourself. you can write code similar to the following:
Cnamedlg DLG;
DLG. m_name = "new name ";
If (DLG. domodal () = idok) MessageBox (DLG. m_name, "Greetings ");
To automatically process the modal dialog box.
The following describes how to use the non-modal dialog box. the dialog box still processes the oninitdialog message, so the initial data transmission is normal. however, non-modal dialog boxes generally do not support pressing the OK button to process their data. This means that we must process data transmission by ourselves. For details, see "quick DDX 』.
Now let's take a look at DDV dynamic data verification. you can also use data verification when using DDX Dynamic Data Exchange. typically, Verification ensures that the number of characters in a string is smaller than the given number, or the number is within a certain range.
However, data verification usually does not meet our expectation, because verification only occurs when the control is transferred to the variable. this usually means that the user enters all the data, click OK, and then receives an error message.
However, we can improve the performance by using "on-site data verification 』.
Improved DDX/DDV
Fast DDX
In several cases, we need to use Quick DDX. For example, if you write an email program, you can enter the name and address in the dialog box, you need a button for the application to get the mail name and address after the user inputs. you can also consider the implementation of the "Apply" button in the modal dialog box.
Of course, to implement the above task, we can directly call getdlgitemtext to obtain the edit box data, but why not use DDX? In this way, at least the dialog box looks a little automated. You can call updatedata (true) to send data to the variable. In turn, you can call updatedata (false) when filling the address ).
To obtain the status of each control to determine when data exchange is required, you can reload the oncommand function of the cdialog class, because the traditional control usually uses the wm_command message to prompt the status change, of course, the new control that uses wm_notify messages can process the onnotify function in the same way. the following is a simple example of using this technology. When you enter data in the dialog box, the data in the main window is changed accordingly.
Program list: Fast DDX
// Livedialog. cpp: implementation file
//
# Include "stdafx. H"
# Include "custom. H"
# Include "livedialog. H"
# Ifdef _ debug
# Define new debug_new
# UNDEF this_file
Static char this_file [] = _ file __;
# Endif
//////////////////////////////////////// /////////////////////////////////////
// Clivedialog Dialog
Clivedialog: clivedialog (cwnd * pparent/* = NULL */)
: Cdialog (clivedialog: IDD, pparent)
{
// {Afx_data_init (clivedialog)
M_email = _ T ("");
M_name = _ T ("");
//} Afx_data_init
M_pview = NULL; // pointer of the application window
}
Void clivedialog: dodataexchange (cdataexchange * PDX)
{
Cdialog: dodataexchange (PDX );
// {Afx_data_map (clivedialog)
Ddx_text (PDX, idc_email, m_email );
Ddx_text (PDX, idc_name, m_name );
//} Afx_data_map
}
Begin_message_map (clivedialog, cdialog)
// {Afx_msg_map (clivedialog)
//} Afx_msg_map
End_message_map ()
//////////////////////////////////////// /////////////////////////////////////
// Clivedialog message handlers
Bool clivedialog: oncommand (wparam, lparam)
{
Bool Fok = cdialog: oncommand (wparam, lparam );
// Don't do if this command destroyed us or we are initializing
If (: iswindow (m_hwnd )&&! In_init)
{
Updatedata (); // update on any change
Assert (m_pview! = NULL );
M_pview-> getdocument ()-> updateallviews (null );
}
Return Fok;
}
Bool clivedialog: oninitdialog ()
{
In_init = true; // initializing
Cdialog: oninitdialog ();
In_init = false; // initialization ends
Return true;
}
In the window view class, use the following method call dialog box:
Void cliveview: ongo ()
{
M_dlg.m_pview = This; // set m_dlg.m_pview to point to the current view
M_dlg.domodal ();
}
Note the following details about the program:
1. The wm_command message has a chance to destroy the dialog box and make it invalid. That is why updatedata is not called when: iswindow (m_hwnd) returns false.
2. if you call updatedata (false) in a part of the code, the control may activate the command message at that time, so that an asserted will be generated when you call updatedate (false) recursively. that is to say, before calling updatedate, you must ensure that you are not updating data. This is why the in_ini flag is set in the oninitdialog function (updatedate (false) is called in oninitdialog )), similarly, this should be done in any other place that calls updatedate.
3. the primary view must know when to update. In this example, save the pointer to the primary view first, and then call the updateallview of the primary view document to update the view when something occurs. you may say that we can find the parent window of the dialog box without having to save the primary view pointer in advance, but this is not feasible, because the parent window of the dialog box can never be viewed (the parent window of the dialog box must be a top-level window ).
Field data verification
The function that implements DDV data verification is actually a function starting with DDV _ in dodataexchange. For example, ddv_minmaxint () is used to verify the Integer Range, ddv_maxchars is used to verify the number of characters in the string, and updatedata () is called () the function can cause the execution of these functions.
To improve data verification, for example, if you want to verify the size when the data in the data input box is changed, instead of waiting for OK to get an error dialog box (like ddv_maxchars, you can verify the number of strings when the data changes), we can reload the oncommand function of the dialog box to intercept all command messages and filter the messages that meet the requirements (such
En_change means that the content in the edit box has changed.) This is a good time to verify the value of the field.
Another problem is that we don't want to verify everything. There are several ways to solve this problem,
For example, manually change the data ing:
Step 1: Add a member variable uint m_vid and set it to 0 in the constructor (when it is 0, execute
In oncommand, save the ID of the control to be verified (extract wparam's
Base character ).
Bool caboutdlg: oncommand (wparam, lparam)
{
If (hiword (wparam) = en_change &&
! M_fisupdating) // check whether the flag is performing data verification or update
{
Updatedata ();
M_vid = 0;
}
Return cdialog: oncommand (wparam, lparam );
}
Step 2: Modify the data ing. Remove all data ing in dodataexchange from Class Wizard
And modify the Code as follows:
Void clivedlg: dodataexchange (cdataexchange * PDX)
{
Cdialog: dodataexchange (PDX );
// {Afx_data_map (caboutdlg)
//} Afx_data_map
M_fisupdating = true; // indicates whether data is being transferred or verified. Set it.
If (! M_vid | m_vid = idc_log)
{
Ddx_text (PDX, idc_log, m_log );
Ddv_maxchars (PDX, m_log, 10 );
}
If (! M_vid | m_vid = idc_num)
{
Ddx_text (PDX, idc_num, m_num );
Ddv_minmaxint (PDX, m_num,-10, 10 );
}
M_fisupdating = false; // clear the flag
}
Here we will also encounter the 2nd problem in the previous example, that is, when you verify a special domain, you must be sure that you are not verifying, or you will get an asserted. Of course, you can use a method similar to the above example, that is, set m_fisupdating = true before data verification (data transmission or verification in progress), and then set it to false after verification, determine the flag before calling updatedata.
However, there is a small problem here. If data verification fails, MFC will issue an exception to discard dodataexchange, so that the flag you set will not work. the simplest solution is to re-set the m_fisupdating flag to false after updatedata is called (it must be after all the updatedata calls, of course, including calls in the MFC internal code, such as cdialog :: in oninitdialog), another method is to capture the exception and then clear the flag. For details, see the following code:
Void clivedlg: dodataexchange (cdataexchange * PDX)
{
Cdialog: dodataexchange (PDX );
// {Afx_data_map (caboutdlg)
//} Afx_data_map
M_fisupdating = true; // indicates whether data is being transferred or verified. Set it.
Try
{
If (! M_vid | m_vid = idc_log)
{
Ddx_text (PDX, idc_log, m_log );
Ddv_maxchars (PDX, m_log, 10 );
}
If (! M_vid | m_vid = idc_num)
{
Ddx_text (PDX, idc_num, m_num );
Ddv_minmaxint (PDX, m_num,-10, 10 );
}
M_fisupdating = false; // clear the flag
}
Catch (...) // catch an exception
{
M_fisupdating = false; // clear the flag
Throw; // throw an exception
}
}
Another method is to refer to the source code of MFC and make the following changes in oncommand:
Bool caboutdlg: oncommand (wparam, lparam)
{
If (hiword (wparam) = en_change &&! M_fisupdating)
// Prevent control specifications from being dispatched during updatedata
{
M_vid = loword (wparam );
_ Afx_thread_state * pthreadstate = afxgetthreadstate ();
Hwnd hwndoldlockout = pthreadstate-> m_hlockoutpolicywindow;
If (hwndoldlockout! = M_hwnd) // must not recurse
Updatedata ();
M_vid = 0;
}
Return cdialog: oncommand (wparam, lparam );
}
Data ing
We should realize that the so-called data ing is just a function, which shows many possibilities (that is, scalability). In addition, you can customize your own data verification, for example, you want to customize a zip code for data verification. for specific implementation methods, see the customized DDX/DDV section below.
Note that you must add the verification code immediately after the corresponding DDX call. Otherwise, when the verification fails, your program may not correctly identify which domain has a problem.
Custom DDX/DDV
Now you can write your own data exchange and data verification processes. You need to know that the exchange and verification functions are just global functions that know how to handle cdataexchange objects.
Let's take a look at the specific practices:
For data exchange, you need to write a global function with the cdataexchange parameter, a control ID, and a variable reference. Although you can not add the DDX _ prefix before the function, but in order to be able to integrate with the Class Wizard, it is best to hold back your thoughts (you will see why later ).
In the exchange function, you can check the cdataexchange pointer to learn the details you need. Next let's take a look at the cdataexchange class members.
Member description
M_bsaveandvalidate
Corresponds to the parameter you provide to updatedata. When it is true, data is transmitted from the control to the variable.
M_pdlgwnd
Handle of the control window or dialog box
Preparectrl (int nidc)
Call this function to identify the current control (for example, it is not an edit box)
Prepareeditctrl (int nidc)
Call this function to identify the current control (such as the edit box)
Fail ()
An error occurred while generating the verification of the control (you can call this function in DDX or DDV). An exception is thrown to destroy the execution of the dodataexchange function.
Generally, to write an exchange function, you must first check the value of m_bsaveandvalidate to determine the data transmission direction. If the transfer fails, you need to call prepareeditctrl (suitable for editing controls) or preparectrl (suitable for all controls). After this call, any call to fail will result in the focus being returned to the control, even if other processes (such as a verification process) publishing the same failure is similar to writing an exchange function. The difference is only the difference between parameters. the function is prefixed with DDV _. The parameter can accept a cdataexchange pointer, a value of the appropriate type, one or two parameters.
It works very easily. If m_bsaveandvalidate is set to true, make sure that the value is valid (it is generally considered that the value passed from the program to the control is correct ). if the data is normal, return from this function. If the data is abnormal, call the Fail function. the previous data exchange function has identified the control of the current operation (that is, why the verification code of DDV must be added immediately after the corresponding DDX call)
Cause ).
The following example demonstrates how to customize your own data verification:
Program list: Use the customized DDX/DDV
// Validview. cpp: Implementation of the cvalidview class
//
# Include "stdafx. H"
# Include "valid. H"
Typedef Float Currency; // used for DDV
# Include "validdoc. H"
# Include "validview. H"
# Include "customdd. H"
# Ifdef _ debug
# Define new debug_new
# UNDEF this_file
Static char this_file [] = _ file __;
# Endif
//////////////////////////////////////// /////////////////////////////////////
// Cvalidview
Implement_dyncreate (cvalidview, cformview)
// Class Wizard won't put this here because it thinks
// Dialog boxes handle onok. They do, but this is
// Form view, not a dialog box
Begin_message_map (cvalidview, cformview)
// {Afx_msg_map (cvalidview)
On_command (idok, onok)
//} Afx_msg_map
End_message_map ()
//////////////////////////////////////// /////////////////////////////////////
// Cvalidview construction/destruction
Cvalidview: cvalidview ()
: Cformview (cvalidview: IDD)
{
Validating = false;
Vid = 0;
// {Afx_data_init (cvalidview)
M_age = 18;
M_name = _ T ("");
M_wager = 1.0;
M_btnenable = true;
//} Afx_data_init
// Todo: Add construction code here
}
Cvalidview ::~ Cvalidview ()
{
}
Void cvalidview: dodataexchange (cdataexchange * PDX)
{
Cformview: dodataexchange (PDX );
// {Afx_data_map (cvalidview)
Ddx_text (PDX, idc_age, m_age );
Ddv_minmaxint (PDX, m_age, 18,150 );
Ddx_text (PDX, idc_name, m_name );
Ddv_maxchars (PDX, m_name, 64 );
Ddx_text (PDX, idc_wager, m_wager );
Ddv_minmaxcurrency (PDX, m_wager, 1.f, 100.f );
Ddx_enablewindow (PDX, idok, m_btnenable );
//} Afx_data_map
}
Bool cvalidview: precreatewindow (createstruct & CS)
{
// Todo: Modify the window class or styles here by modifying
// The createstruct CS
Return cformview: precreatewindow (CS );
}
//////////////////////////////////////// /////////////////////////////////////
// Cvalidview diagnostics
# Ifdef _ debug
Void cvalidview: assertvalid () const
{
Cformview: assertvalid ();
}
Void cvalidview: dump (cdumpcontext & DC) const
{
Cformview: dump (DC );
}
Cvaliddoc * cvalidview: getdocument () // non-debug version is inline
{
Assert (m_pdocument-> iskindof (runtime_class (cvaliddoc )));
Return (cvaliddoc *) m_pdocument;
}
# Endif // _ debug
//////////////////////////////////////// /////////////////////////////////////
// Cvalidview message handlers
Void cvalidview: onok ()
{
If (updatedata (true ))
{
MessageBox ("wager placed ");
M_btnenable = false;
Updatedata (false );
}
}
Program list: customized DDX/DDV Process
# Include <stdafx>
# Include "customdd. H"
// Custom exchange
Void ddx_enablewindow (cdataexchange * PDX, int ID, bool & flag)
{
Cwnd * CTL = PDX-> m_pdlgwnd-> getdlgitem (ID );
If (PDX-> m_bsaveandvalidate)
Flag = CTL-> iswindowenabled ();
Else
CTL-> enablewindow (FLAG );
}
// Valim validator
Void ddv_minmaxcurrency (cdataexchange * PDX, float Val, float min, float max)
{
Cwnd * editctl = cwnd: fromhandle (PDX-> m_hwndlastcontrol );
Cstring S;
Int N;
If (PDX-> m_bsaveandvalidate)
{
// Using math to decide if anything is left over is bad because of rounding
// Errors, so use a string method instead
Editctl-> getwindowtext (s );
N = S. Find ('.');
If (n! =-1 & N + 3 <s afxmessagebox enter the data to nearest penny PDX-> fail ();
}
Ddv_minmaxfloat (PDX, Val, Min, max); // Let the existing one do the job
}
}
Integration with Class Wizard
If you only want to apply a custom process to a project, you can add it to the CLW file of the project. you can also include mfcclwz. the bin directory of the DLL file (My is... /Microsoft Visual Studio/common/msdev98/bin) to create a ddx. CLW file, and then your DDX process can be applied to all projects.
Let's take a look at how to write CLW files:
First, add a section named [extraddx], which looks like the section of the INI file, but the name here is case sensitive.
[Extraddx]
Extraddxcount = 2
Extraddx1 = E; value; currency; 0.0; text; floating point currency; minmaxcurrency; Mi & nimum; F; Ma & ximum; f
Extraddx2 = bbeccr169n; enable State; bool; true; enablewindow; window enabled status
What do these codes mean?
Row 2
Extraddxcount = x
"X" indicates the number of projects (2 in this example), and then the next line starts with "extraddx1 =", and then extraddx2 =, extraddx3 =, and so on. The Code on the right of the equal sign is divided into 7, 10, or 12 domains, depending on what you want to achieve. Each domain is separated by a semicolon, the following table lists the meanings of these domains:
Domain
Description
1
The spatial type of the DDX application (for example, E = edit box)
2
Unused
3
Attribute type (often a value, corresponding to the first combo box of Class Wizard)
4
Variable data type
5
Initial Value
6
DDV process name without the DDV _ prefix
7
Note
8
DDV process name without the DDV _ prefix
9
Name of the first DDV parameter (optional)
10
Type of the first DDV parameter (for example, F = float; optional)
11
Name of the second DDV parameter (optional)
12
Type of the second DDV parameter (for example, F = float; optional)
You don't have to specify any DDV process. You can mix a new verification process with a standard exchange function (just like what extraddx1 does ). note that these DDX and DDV function names do not start with DDX _ and DDV _, but Class Wizard does add these prefixes to the code it generates. this is why the functions are named with these prefixes.