On my current project I have been struggling the last couple of days with subclassing edit boxes. By now I successfully subclassed my edit box and validated the input such that it only accepts digits, commas, a minus sign and keyboard commands.
But for quite a while I'm now stuck with the refinement of the input validation. I want my edit box to behave as the following:
- accept minus sign only at first position
- accept only one leading zero
- accept only one comma
- force comma after leading zero
- manage those cases when deleting single characters or selections of the text via 'back', 'delete', select-all and then pasting something over it
My code in its current form looks like this and provides almost none of the advanced validation requirements I specified above:
inline LRESULT CALLBACK decEditBoxProc(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData)
{
if(msg == WM_CHAR)
{
decEditBoxData* data = reinterpret_cast<decEditBoxData*>(ULongToPtr(dwRefData));
bool isDigit = (wParam >= '0' && wParam <= '9');
bool isZero = ((wParam == '0') && !data->blockZero);
bool isSign = (wParam == '-');
bool isComma = ((wParam == '.' || wParam == ',') && !data->blockComma);
bool isValidCommand = (wParam == VK_RETURN
|| wParam == VK_DELETE
|| wParam == VK_BACK);
// Restrict comma to one.
if(isComma && data->nCommas > 0)
return FALSE;
else if(isComma && data->nCommas == 0)
data->nCommas++;
// Restrict trailing zeroes to one.
if(isZero && data->nTrailingZeroes > 0)
return FALSE;
else if(isZero && data->nTrailingZeroes == 0)
data->nTrailingZeroes++;
// Filter everything but digits, commas and valid commands.
if(!isDigit && !isValidCommand && !isComma)
return FALSE;
}
return DefSubclassProc(hWnd, msg, wParam, lParam);
}
Any idea on how to algorithmically solve this problem is very appreciated.
UPDATE
Thanks to the suggestions of David Heffernan and IInspectable I was able to (almost) solve my problem without subclassing the edit controls.
In the dialog procedure (thats contains the edit controls):
switch(msg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_IN_REAL:
if(HIWORD(wParam)==EN_CHANGE) onEditChange(hDlg, IDC_IN_REAL);
break;
case IDC_IN_IMAG:
if(HIWORD(wParam)==EN_CHANGE) onEditChange(hDlg, IDC_IN_IMAG);
break;
}
break;
}
With onEditChange:
void onEditChange(HWND hDlg, int ctrlID)
{
HWND hEdit = GetDlgItem(hDlg, ctrlID);
size_t len = GetWindowTextLength(hEdit)+1;
wchar_t* cstr = new wchar_t[len];
GetWindowText(hEdit, cstr, len);
std::wstring wstr(cstr);
if(!(tools::isFloat(wstr)))
{
EDITBALLOONTIP bln;
bln.cbStruct = sizeof(EDITBALLOONTIP);
bln.pszTitle = L"Error";
bln.pszText = L"Not a valid floating point character.\nUse '.' instead of ','";
bln.ttiIcon = TTI_ERROR;
Edit_ShowBalloonTip(hEdit, &bln);
}
delete [] cstr;
}
and isFloat():
bool tools::isFloat(std::wstring str)
{
std::wistringstream iss(str);
float f;
wchar_t wc;
if(!(iss >> f) || iss.get(wc))
return false;
return true;
}
I will probably add some more visual feedback for the user, but that's not important right now.
The question however, is not answered yet. My intention was to allow the "," as a possible decimal point.