Did you know you can add your own DDX and DDV handlers directly to Class Wizard? It's true. Here's what you need to do:
1. Write a global function that begins with DDX_ (or DDV_)
2. Your DDX function should take a pointer to a CDataExchange object, an integer control ID, and a reference to whatever type of data you will exchange.
3. DDV function take a pointer to a CDataExchange object, a value, and one or two validation limits.
4. If your exchange or validation might fail, you must call CDataExchange::PrepareCtrl (or CDataExchange::PrepareEditCtrl.
5. The CDataExchange object has a flag named m_bSaveAndValidate that tells you if you are reading to the variables or writing from the variables.
6. If you decide the data is wrong in either the DDX or DDV routine, call CDataExchange::Fail.
How do you integrate it into Class Wizard? You have to make an entry in your CLW file (or in the global DDX.CLW file that appears in the SharedIDE\BIN directory.
The line has several fields separated by semicolons (you can find out all the details in the online help).
Here is an example DDX.CLW:
[ExtraDDX] ExtraDDXCount=1 ExtraDDX1=E;;Value;Currency;0.0;Text;Floating Point Currency;MinMaxCurrency;Mi&nimum;f;Ma&ximum;f
The first field tells Class Wizard what controls this exchange is valid for. E mean edit control. You can specify more than one letter, if your exchange handles multiple control types. The second field is unused. The third field corresponds to the first combo box in Class Wizard's Add Variables dialog. You'll usually use Value here (as opposed to the other usual choice: Control). However, you can also make up your own new categories here.
The fourth field is the data type for the variable. Here I use Currency which is just a typedef for long. After that is the default value for the variable. Next is the DDX routine to use (without the DDX_ prefix). Notice that I am simply using the standard DDX_Text routine.
The final fields describe the transfer, name the DDV routine (again without the prefix) and specify the name and type of the validation variables (in this case a minimum and maximum value).
Here is the validation code:
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.GetLength())
{
AfxMessageBox("Please enter the data to the nearest penny");
pDX->Fail();
}
DDV_MinMaxFloat(pDX,val,min,max); // let the existing one do it
}
}
It is interesting to notice that this routine checks that there isn't more than two numbers after the decimal, but then it calls a standard DDV routine to validate the floating point number format.
As a second example, Here's what DDX_UpperText might look like. This is a routine that transfers text and shifts it to upper case at the same time.
void AFXAPI DDX_UpperText(CDataExchange* pDX, int nIDC, CString& value)
{
HWND hWndCtrl = pDX->PrepareEditCtrl(nIDC);
if (pDX->m_bSaveAndValidate)
{
int nLen = ::GetWindowTextLength(hWndCtrl);
::GetWindowText(hWndCtrl, value.GetBufferSetLength(nLen), nLen+1);
value.ReleaseBuffer();
value.MakeUpper();
}
else
{
// by custom, you don't convert data on the way out
AfxSetWindowText(hWndCtrl, value);
}
}
You'll find more about these techniques in my book MFC BlackBook.