Block characters in Edit Control using MFC (Example for only floats) - c++

I'm trying to block certain types of characters from being inserted into my Edit Control by overwriting OnChar and OnKeydown. I'm trying to block more than one point '.' and anything that is not a number.
First I check if there is already a '.' in the Edit Control that has the focus by setting a variable defined in the dialog class to false:
void MyMainDialog::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CWnd * eb1 = GetDlgItem(IDC_EDIT1); //Reference dimension 1 box;
CWnd * eb2 = GetDlgItem(IDC_EDIT2); //Reference dimension 2 box
CWnd * eb3 = GetDlgItem(IDC_EDIT3); //Reference dimension 3 box
CString temp;
CWnd * focusedHand = MyMainDialog::GetFocus(); //Reference edit box being focused
if(focusedHand == eb1)
{
eb1->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
else if(focusedHand == eb2)
{
eb2->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
else if(focusedHand == eb3)
{
eb3->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
CDialogEx::OnKeyDown(nChar, nRepCnt, nFlags);
}
At OnChar I'm checking the character being typed. If it is not a number of if it's a point but there was already a point, then I don't call the OnChar from CDialog:
void MyMainDialog::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar == '.' && checkPoint == false) //Is a point and there is no other point
{
CDialogEx::OnChar(nChar, nRepCnt, nFlags);
}
if((nChar < '0' || nChar > '9')) //Is not a number
{
//Show message to user
}
else //Is a number
{
CDialogEx::OnChar(nChar, nRepCnt, nFlags);
}
}
Well, my code is not working. It compiles and it doesn't crash when typing in the edit control, but it simply doesn't do anything. I'm wondering if the right way to overwrite it would be to prevent the call of CDialogEx::OnChar() or if I should make nChar = 0 so that the character displayed will be null. But on top of that the message that I'm trying to display at OnChar is also not displaying, meaning that MyMainDialog::OnChar() is not even being called. Should I overwrite CDialogEx::OnChar() instead?
Thanks for your attention

A much easier way is to handle your input with an OnChange event:
// do not add numbers; ascci numbers is 48 - 57
if ((m_strYOURCONTROL[m_strYOURCONTROL.GetLength() - 1]) > 47 &&
m_strYOURCONTROL[m_strYOURCONTROL.GetLength() - 1]) < 58)
{
m_strYOURCONTROL = m_strYOURCONTROL.Mid(0, m_strYOURCONTROL.GetLength() - 1);
}
This will not allow numbers. With this implementation you can handle easier the input to the editbox

I found the solution. The reason why the code doesn't seem to be making any effect in my application is because it is making effect only to MyMainDialog. When typing in the Edit Control, the OnChar() being called if from CEdit, so that is the one I have to intercept. You are not allowed to overwrite CEdit::OnChar(). The solution is to create a class that derives from CEdit and then intercept the OnChar() from that class. You you also have to use your class instead of CEdit for manipulating your Edit Control.
My code was then reformulated to this:
OnChar():
void CustomEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar == '.' && checkPoint == true) //Is a point and there is already a point
{
//Show message
}
else if(nChar == '.' && checkPoint == false) //Is a point but there is no point
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
if((nChar < '0' || nChar > '9') && nChar != 8) //Is not a number or backspace
{
//Show message
}
else //Valid
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
}
OnKeyDown():
void CustomEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CString temp;
this->GetWindowTextA(temp);
if(temp.Find('.') == -1)
checkPoint = false; //There is no point
else
checkPoint = true; //There is a point
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
Hope this helps anyone with a similar question. By the way, there are many classes and automated solutions in the internet, I did this manually for learning purposes.

Another solution is to handle WM_CHAR earlier, in PreTranslateMessage.

Related

treeview 3-state and 2-state checkboxes on different nodes

I have to create a treeview control in a class that inherits from CTreeCtrl. After many tries, I have created 3-state checkboxes for every node. And here comes my question - I need some child nodes to have only 2 states (checked or unchecked). Any clue on how can I reach that goal? I tried to add an if statement to change the behavior like this:
else if (itemState == INDEXTOSTATEIMAGEMASK(3))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
And it’s working! Except when an element is "selected", then it’s like ignoring every behavior I did in the OnLButtonClick event.
Is there some other event I don’t know about? Is there an easier way to get a 2-state checkbox only on some nodes and not others?
This is all i use to control the tree
void CSelDepTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
CTreeCtrl::OnLButtonDown(nFlags, point);
UINT Flags = 0;
HTREEITEM hItem = HitTest(point, &Flags);
if ((NULL != hItem) && (TVHT_ONITEMSTATEICON & Flags))
{
auto itemText = GetItemText(hItem);
if (itemText.Left(1) == "C") {
if ((nFlags & (MK_SHIFT | MK_CONTROL)) || NULL == GetParentItem(hItem)) {
SetCheckChild(hItem, GetCheck(hItem));
}
}
if (itemText.Left(1) == "L") {
if ((nFlags & (MK_SHIFT | MK_CONTROL)) || NULL == GetParentItem(hItem)) {
SetCheckChild(hItem, GetCheck(hItem));
}
}
if (itemText.Left(1) == "S") {
auto itemState = GetItemState(hItem, TVIS_STATEIMAGEMASK);
if (itemState == INDEXTOSTATEIMAGEMASK(1))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(2), TVIS_STATEIMAGEMASK);
}
else if (itemState == INDEXTOSTATEIMAGEMASK(2))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
}
else if (itemState == INDEXTOSTATEIMAGEMASK(3))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
}
}
}
}

I want to display item lastly checked in tree control mfc

I tried this
void GetCheckedItems(const CTreeCtrl& tree, CArray<HTREEITEM> *checkedItems, HTREEITEM startItem = NULL)
{
if (startItem == NULL)
startItem = tree.GetRootItem();`
for (HTREEITEM item = startItem; item != NULL; item = tree.GetNextItem(item, TVGN_NEXT))
{
// figure out if this item is checked or not
UINT state = (tree.GetItemState(item, TVIS_STATEIMAGEMASK) >> 12) & 15;
if (state == 2)
checkedItems->Add(item);
// deal with children if present
HTREEITEM child = tree.GetNextItem(item, TVGN_CHILD);
if (child != NULL)
GetCheckedItems(tree, checkedItems, child);
}
}
now I have an array contains checked values but how do i know which i checked last time.
Just use a handler to TVN_ITEMCHANGED. It is called whenever an item state is changing.
You need to check the state flags that are changed. You can detect changes to the TVIS_STATEIMAGEMASK.
CString m;
HTREEITEM selItem;
selItem=m_treeRel.GetSelectedItem();
UINT uFlags = 0;
CPoint pt(0, 0);
GetCursorPos(&pt);
m_treeRel.ScreenToClient(&pt);
HTREEITEM hItem = m_treeRel.HitTest(pt, &uFlags);
if(NULL != hItem && (TVHT_ONITEM & uFlags))
{
/*if(selItem == hItem)
{
m=_T("Selected Item....");
}
else */
if(!m_treeRel.GetCheck(hItem))
{
m=m_treeRel.GetItemText(hItem);
//m_treeRel.SetCheck(hItem,true);
}
else
{
//m=_T("ERROR");
//m_treeRel.SetCheck(hItem,false);
}
}
ShowMessage(m);
*pResult = 0;
I Used This to find out which item was checked last time....!

Input validation of a subclassed edit control to only accept floating point numbers

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.
You need a state machine. First declare your states.
enum InputState
{
NoCharacters
MinusSign
LeadingZero
PreDecimalPoint
PostDecimalPoint
}
InputState mState = NoCharacters
Whenever the user inputs a character, call a different validation function depending on the value of mState using a switch block.
bool ValidCharacter(char input)
{
switch (mState)
{
case NoCharacters:
return NoCharacters(input);
case MinusSign:
return MinusSign(input);
/// etc
}
}
So for example, the function you call when mState == NoCharacters, would accept any number, decimal point or minus sign.
It would then change mState to MinusSign if the character was a minus sign, LeadingZero if it was a zero, etc.

To disable spaces entering textbox

In MFC How to disable spaces entering textbox
Just supply the own OnKeyDown event handler and filter out the space keys:
void MyEditControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == 32)
{
// kill the space key down event
return;
}
// let edit control handle the other keys
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
you can update the user entry on OnChangeControl as follows:
if ((m_strYOURCONTROL[m_strYOURCONTROL.GetLength() - 1]) == ' ')
{
m_strYOURCONTROL = m_strYOURCONTROL.Mid(0, m_strYOURCONTROL.GetLength() - 1);
}

MFC OnEnChange handler function - infinite loop

(I'm using VS++2005)
I put edit box control (with ID - ID_edit_box) on my dialog, and associate (with handler wizard) two varibles for it: control (c_editbox) and value (v_editbox) variable. Also I associate handler function OnEnChangeedit_box with that edit box control. Suppose that we may enter just one digit in edit box, and that digit can be 0 or 1. If we enter some other value - what I want is that content of that edit box is automaticaly cleared, so user can't see that he type anything (in other words user can not enter anything except 0/1 in edit box). I do that check in onEnChangeedit_box function. Here is the code:
void CSDRDlg::OnEnChangeedit_box()
{
CWnd* pWnd;
CString edit_box_temp;
pWnd = GetDlgItem(ID_edit_box);
pWnd->GetWindowText(edit_box_temp);
if ((edit_box_temp == "0" || edit_box_temp == "1")
{...do something - i.e. setfocus on some other edit box }
else
{
pWnd->SetWindowText(""); // clear the content of edit box
//... any other statement below will not be executed because the
//above line cause again call of this function
}
}
I debug and discover that line: pWnd->SetWindowText(""); cause an infinite loop because we change control content in this function which triggers again her call.
But I change above code like this:
void CSDRDlg::OnEnChangeedit_box()
{
UpdateData(TRUE);
if ((v_editbox == "0" || v_editbox== "1")
{...do something - i.e. setfocus on some other edit box }
else
{
v_editbox = "";
UpdateData(FALSE);
}
}
and that works what I want but can someone explain to me why when we call
v_editbox = "";
UpdateData(FALSE);
that doesn't cause an infinite loop.
Why don't you Set Min/Max value to 0/1 or Set Value Type as bool when you Add Variable for your EditBox
You should probably do this by subclassing CEdit and filter out invalid characters. eg:
class CSDREdit public CEdit
{
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CSDREdit, CEdit)
ON_WM_CHAR()
END_MESSAGE_MAP()
void CSDREdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar != _T('0') && nChar != _T('1'))
return;
CEdit::OnChar(nChar, nRepCnt, nFlags);
}