MFC OnEnChange handler function - infinite loop - mfc

(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);
}

Related

Detecting Up / Down arrows in CSpinButtonCtrl MFC C++

Is there any way to differentiate when the Up or Down arrow of a CSpinButtonCtrl is pressed?
I am trying to use the OnPointerdown event, but I don't know how to do it...
afx_msg LRESULT CMySpinButtonCtrl::OnPointerdown(WPARAM wParam, LPARAM lParam)
{
if(IS_POINTER_PRIMARY_WPARAM(wParam))
{
//TODO
}
return 0;
}
I will appreciate any kind of help.
Is there any way to differentiate when the Up or Down arrow of a CSpinButtonCtrl is pressed?
You should use UDN_DELTAPOS to do this.
Right-click the control in the Resource Editor and select Add Event Handler:
Select the UDN_DELTAPOS message and click Add and Edit:
You will be provided with skeleton code:
void CMFCApplication1Dlg::OnDeltaposSpin1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}
The NMUPDOWN article explains about the structure that you use. What you need to do is test the iDelta value. Example:
void CColumnOrderDlg::OnDeltaposSpinColumns(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
if (pNMUpDown != nullptr)
{
if( pNMUpDown->iDelta > 0)
// Up - Do stuff;
else if(pNMUpDown->iDelta < 0)
// Down - Do stuff;
}
*pResult = 0;
}
There is also a useful article here where it states:
If you use a spin control for some other purpose, for example, to page through a sequence of windows or dialog boxes, then add a handler for the UDN_DELTAPOS message and perform your custom action there.

How to avoid EN_CHANGE notifications when sending WM_SETTEXT?

I have a CEdit derived control that displays the string "N/A" when the undelying data is null. I recently added code to empty the control(SetWindowText("");) when it gains focus and set if back to "N/A"(SetWindowText("N/A")) when the focus is lost if the user left the control empty.
The only problem is that setting the window text to "" or "N/A" triggers EN_CHANGE, so my dialog thinks that the data has changed.
How can I avoid EN_CHANGE from being fired when calling SetWindowText (WM_SETTEXT)?
NOTES
-I know I can set the edit control to Multiline=TRUE but that's not accpectable for me.
-My application is MBCS so I can't use SetCueBanner
-I want an elegant solution. Setting the parent window to NULL temporarily is not an elegant solution.
EDIT:
-I want the solution to be in my custom control, not in each dialog
Thanks
The way I've done it before (last time, like 20 minutes ago; in fact I was thinking about asking the same question), is by setting a flag. When I'm about to set the text programatically, I set the flag, and I check it in the EN_CHANGE handler:
void CMyDialog::MyFunction()
{
setEditTextProgramatically = true;
c_Edit.SetWindowText(_T("Whatever"));
setEditTextProgramatically = false;
}
void CMyDialog::OnEnChangeEdit()
{
if (!setEditTextProgramatically)
{
// Do whatever you need to do
}
}
I know it's not the most elegant solution, but it works, at least for me.
I've always wondered why MFC doesn't provide a way to distinguish user input from changes from code, but that's the way it is.
I finally found a suitable solution to my problem.
First, I added a flag to my derived control's header file and I initialized it to false in the constructor
bool m_bNoEnChange;
I overrode the OnChildNotify in my derived control's header file and in the implementation, I checked for the WM_COMMAND message with the EN_CHANGE parameter. I then returned TRUE to prevent the message from being sent to the parent(dialog/page)
virtual BOOL OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult);
BOOL CADEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
{
if(message == WM_COMMAND && HIWORD(wParam) == EN_CHANGE)
{
//If the flag is set, don't send the message to the parent window
if(m_bNoEnChange)
return TRUE;
}
return CEdit::OnChildNotify(message, wParam, lParam, pLResult);
}
Finally, when the control gains and loses focus, I wrapped the problematic SetWindowText with my flag
m_bNoEnChange = true;
SetWindowText(_T(""));
m_bNoEnChange = false;
This solution is the best in my case because I don't have to modify each dialog.
You could disable (EnableWindow(FALSE) or send WM_ENABLE with param FALSE) the control prior to sending WM_SETTEXT then enabling it afterwards. That should prevent the EN_CHANGE
There is probably some more elegant method :p
The below code uses a C++ 11 feature, but that can easily be changed.
HEADER
// CEditOptionalNotify.h
//
// CEdit derived class allowing the control's text value to be
// set without (optionally) causing EN_CHANGE processing.
//
#pragma once
class CEditOptionalNotify : public CEdit
{
//DECLARE_DYNAMIC(CEditOptionalNotify)
// Enable use of RUNTIME_CLASS macro and CObject::IsKindOf()
public:
CEditOptionalNotify();
virtual ~CEditOptionalNotify();
enum class PerformOnChangeProcessing { No, Yes };
void vSetText(const TCHAR* pText, PerformOnChangeProcessing e);
protected:
afx_msg BOOL bConsiderEnChangeAsHandled();
bool m_bChangeNotificationsEnabled;
DECLARE_MESSAGE_MAP()
};
IMPLEMENTATION
// EditOptionalNotify.cpp : implementation file
//
#include "stdafx.h"
#include <EditOptionalNotify.h>
//IMPLEMENT_DYNAMIC(CEditOptionalNotify, CEdit)
CEditOptionalNotify::CEditOptionalNotify() :
m_bChangeNotificationsEnabled(true)
{
}
CEditOptionalNotify::~CEditOptionalNotify()
{
}
BEGIN_MESSAGE_MAP(CEditOptionalNotify, CEdit)
ON_CONTROL_REFLECT_EX(EN_CHANGE, bConsiderEnChangeAsHandled)
END_MESSAGE_MAP()
BOOL CEditOptionalNotify::bConsiderEnChangeAsHandled()
{
return (m_bChangeNotificationsEnabled ? FALSE : TRUE);
}
void CEditOptionalNotify::vSetText(const TCHAR* pText, PerformOnChangeProcessing e)
{
bool bChangeNotificationsDesired = (PerformOnChangeProcessing::No == e ? false : true);
if (bChangeNotificationsDesired != m_bChangeNotificationsEnabled)
{
m_bChangeNotificationsEnabled = bChangeNotificationsDesired;
CEdit::SetWindowText(pText);
m_bChangeNotificationsEnabled = (bChangeNotificationsDesired ? false : true);
}
else
CEdit::SetWindowText(pText);
}
LRESULT CMainDlg::OnEnUpdateEditID(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
//using static variable
static bool isCodeChangeText = false;
if(isCodeChangeText)
return 0;
……//Deal Window Text
if(old == new)
return 0;
int nSel = m_editPID.GetSel();//record cursor pos
isCodeChangeText = true;
m_editID.SetWindowText(new);
m_editID.SetSel(nSel);
isCodeChangeText = false;
return 0;
}
In case somebody else finds this discussion...
As Steven wrote UpdateData does not cause an EN_CHANGE being sent.
Under the hood MFC calls AfxSetWindowText with which one can specify one hwnd.

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

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.

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);
}

need some help in making slider control

i have made the slider control in c++ using MFC. there is my code.
void CImageAnalyserDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if(nSBCode == SB_THUMBPOSITION)
{
slidervalue.Format(_T("%d"), nPos);
UpdateData(false);
}
else
{
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
}
every thing is done, i just wanna know where should i write the implementaion of slider control, i mean where should i write this
if(slidervalue="10")
{
//do something
}
Why would you want to put the slider position into a string and compare it at some other place in your code?
In the OnHScroll handler, you already got the slider position. Do whatever you want to do in that function, or call some other function from the handler.
You can add an integer variable 'slidervalue' to your slider and set its max and min values to 100 and 0 respectively. Instead of reading the nPos parameter you can read this variable easily.
void CImageAnalyserDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
UpdateData(TRUE);
if(slidervalue==10)
{
//do something
}
}
Hope this helps!