CEdit numeric validation event C++ MFC - c++

I have a CEdit text box which is a part of a property pane and only allows numeric values (positive integers). The box works fine when people enter non-numeric values, but when they delete the value in the box a dialog pops up saying:
"Please enter a positive integer."
Here is the situation:
1. I have a number (say 20) in the box.
2. I delete the number.
3. I get the error dialog.
Could anybody tell me how I can intercept this event and put a default value in there?
Here is what my property pane looks like:
const int DEFAULT_VALUE = 20;
class MyPropertyPane:public CPropertyPane
{
//....
private:
CEdit m_NumericBox;
int m_value;
//....
public:
afx_msg void OnEnChangeNumericBox();
//....
}
void MyPropertyPane::MyPropertyPane()
{
// Set a default value
m_value = DEFAULT_VALUE;
}
//....
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
// this sets the displayed value to 20
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
}
//....
void MyPropertyPane::OnEnChangeNumericBox()
{
// Somebody deleted the value in the box and I got an event
// saying that the value is changed.
// I try to get the value from the box by updating my data
UpdateData(TRUE);
// m_value is still 20 although the value is
// deleted inside the text box.
}

The message you are receiving is coming from the data validation routines, not the data exchange routines. There is probably a call like this in DoDataExchange():
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
DDV_MinMaxInt(pDX, m_value, 1, 20); // if the value in m_value is outside the range 1-20, MFC will pop up an error dialog
}
You can fix this problem by removing the built-in MFC data validation and adding your own:
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
if( m_value < 1 || m_value > 20 )
{
m_value = DefaultValue;
}
}

John Dibling's hint led me to this solution:
void MyPropertyPane::OnEnChangeNumericBox()
{
UpdateData(TRUE);
CString value;
m_NumericBox.GetWindowText(value);
if( value.IsEmpty() )
{
m_value = DEFAULT_VALUE;
UpdateData(FALSE);
}
}
The ONLY validation that I really had to do is to check that the box contains a value, since the actual numeric validation is already handled by the box. The user can't enter a non-numeric value, but they can delete the existing one so that was a situation which was hard to handle in the data exchange function and I had to "hack" the OnChange event.

This one worked for me
void CtimersDlg::OnEnChangeInterval()
{
CString value; //or use char *
CWnd *pWnd = GetDlgItem(IDC_INTERVAL);//IDC_EDITBOX
if(pWnd)
{
pWnd->GetWindowTextW(value);
}
int i = _wtoi(value); //if char * use _atol()
if((!value.IsEmpty())&& (i)) //To check i = 0 or 00 entered or not
UpdateData(TRUE);
}

Related

MFC Dialog Data Validation reverting data on failure

Has anyone found a way to revert a value on an editbox if it fails validation? If the value is invalid, it harasses the user with message boxes until they fix it.
void MyDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_FOO, foo);
DDV_MinMaxFloat(pDX, foo, 0.001f, 300.0f);
}
I was able to do this by writing custom DDX_ handlers. The application I worked on used a custom control (MCReal derived from CEdit) that would only accept decimal values between an acceptable range defined in the control. When the user entered a non-decimal value, or, a value outside of the range, the code would pop up a custom message and revert the value entered into the dialog field.
This was accomplished by creating a custom control and a custom validation handler. Here’s what the DDX_ routine looked like:
void AFXAPI_EXPORT DDX_ProcessEditReal(CDataExchange* pDX, int nIDC, MCReal& mcr)
{
// prepare edit control
HWND hWndCtrl = pDX->PrepareEditCtrl(nIDC);
// does control exist yet?
if (!IsWindow(mcr.m_hWnd))
{
// subclass the control
if (!mcr.SubclassWindow(hWndCtrl))
{
ASSERT(false);
// possibly trying to subclass twice?
AfxThrowNotSupportedException();
}
return;
}
if (!ValidateMCRealCtrl (mcr, pDX->m_pDlgWnd, (pDX->m_bSaveAndValidate == TRUE)))
{
pDX->Fail ();
}
}
I used the standard DDX_ routines as a starting point to write a custom version. The real work is done in ValidateMCRealCtrl():
bool ValidateMCRealCtrl (MCReal &mcRealCtrl, CWnd *pParentWnd, bool bSaveAndValidate)
{
CString ctext;
double val = 0.0, r = 0.0;
double unit_factor = 0.0;
bool bDmsrg = false;
bool rc = false;
bool ret;
...
if (bSaveAndValidate) // Move from dialog to data
{
if (pParentWnd != nullptr && mcRealCtrl.CancelButtonClicked (pParentWnd))
{
return true;
}
if (!mcRealCtrl.IsWindowEnabled () || !mcRealCtrl.IsWindowVisible ())
{
return true;; // don't update if not enabled
}
mcRealCtrl.GetWindowText (ctext);
...
// base field validation.
ret = mcRealCtrl.Validate ();
if (!ret)
{
make_mcreal_str (r, ctext.GetBuffer (mcRealCtrl.maxlen), mcRealCtrl.maxlen, prec, mcRealCtrl.add_plus,
mcRealCtrl.m_strip_trailing == TRUE);
ctext.ReleaseBuffer ();
InvalidRealField (mcRealCtrl); // Bad value
return false; // Reset Focus
}
...
ctext.ReleaseBuffer ();
mcRealCtrl.SetWindowText (ctext);
}
else // Move from data to dialog
{
if (mcRealCtrl.angle) // If angle field...
{
val = mcRealCtrl.value * R2D; // Convert to degrees
}
else
{
val = mcRealCtrl.value; // Use data value
}
make_mcreal_str (val, ctext.GetBuffer (mcRealCtrl.maxlen), mcRealCtrl.maxlen, prec, mcRealCtrl.add_plus,
mcRealCtrl.m_strip_trailing == TRUE);
ctext.ReleaseBuffer ();
mcRealCtrl.SetWindowText (ctext);
mcRealCtrl.SetLimitText (mcRealCtrl.maxlen);
}
...
return true;
}
(Note: I've replaced code that does not pertain to your question with "...")
The work to revert the field value occurs in InvalidRealField (). That code displays a pop up message and uses the previous value of the field (saved within the actual MCReal control class), before it was changed, to revert the value.
This framework was not written specifically to revert incorrect dialog field values. It provides much more than that since the control class provides some extra capabilities. However, having the control defined in a custom class allowed me to provide custom validation.

MFC CListCtrl::SetItemText() not working

I am a beginner in building MFC application. I've just started using list controls (in report view) and I am facing some problems while updating the list. I have three buttons for add, update and delete. Everything works well except the update. Here's the code.
void CAddDetailsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_DEPARTMENT, departmentControl);
DDX_Text(pDX, IDC_NAME, m_name);
DDX_Text(pDX, IDC_ID, m_id);
DDX_Text(pDX, IDC_AGE_BUDDY, m_ageVariable);
DDX_CBString(pDX, IDC_DEPARTMENT, m_department);
DDX_Control(pDX, IDC_LIST1, m_listControl);
}
BOOL CAddDetailsDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: Add extra initialization here
ageSpin=reinterpret_cast<CSpinButtonCtrl*>(GetDlgItem(IDC_AGE_SPIN));
ageBuddy=reinterpret_cast<CEdit*>(GetDlgItem(IDC_AGE_BUDDY));
ageSpin->SetBuddy((ageBuddy));
ageSpin->SetRange32(18,60);
departmentControl.AddString("Human Resource");
departmentControl.AddString("Manager");
departmentControl.AddString("Administrator");
departmentControl.AddString("Desktop Engineer");
m_listControl.InsertColumn(0,"ID",0,100);
m_listControl.InsertColumn(1,"Name",0,100);
m_listControl.InsertColumn(2,"Age",0,60);
m_listControl.InsertColumn(3,"Department",0,100);
m_listControl.SetExtendedStyle(LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT );
m_ageVariable="18";
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CAddDetailsDlg::OnBnClickedEdit()
{
// TODO: Add your control notification handler code here
UpdateData();
if((m_id=="")||(m_name=="")||(m_department=="")||(m_ageVariable==""))
{
MessageBox("Please choose an item to edit","Error");
}
else
{
int index=m_listControl.GetSelectionMark();
m_listControl.SetItemText(index,0,m_id);
m_listControl.SetItemText(index,1,m_name);
m_listControl.SetItemText(index,2,m_ageVariable);
m_listControl.SetItemText(index,3,m_department);
MessageBox("Successfully Updated","Info");
}
}
void CAddDetailsDlg::OnBnClickedNewButton()
{
// TODO: Add your control notification handler code here
UpdateData();
if((m_id=="")||(m_name=="")||(m_department=="")||(m_ageVariable==""))
{
MessageBox("Please fill in all the details","Error");
}
else
{
int count=m_listControl.GetItemCount();
count=m_listControl.InsertItem(count,m_id);
m_listControl.SetItemText(count,1,m_name);
m_listControl.SetItemText(count,2,m_ageVariable);
m_listControl.SetItemText(count,3,m_department);
}
}
Note:-
The update function works fine if I update only the ID. If I try to update all/ many fileds, only the ID gets updated and nothing else. BTW, age is a spinControl, department is a comboBox and the other two are editBox.
Edit:-
I found that both, the value of variable m_name and the editBox value changes to the older values after the line m_listControl.SetItemText(index,0,m_id);. Its the same case with m_age and m_department.
If I comment the line m_listControl.SetItemText(index,0,m_id);, I can update everything at a time except the ID.
I am able to update everything by storing m_name, m_age and m_department in a local variable just before the line m_listControl.SetItemText(index,0,m_id); and using those variables in SetItemText(). But as I'm learning, I wanna know where I'm going wrong.
i think you forget to add UpdateData() before your code which is under Update_Bn_Click because at my side i use your code with updatedata() and its working fine.
UpdateData();
int index=m_List.GetSelectionMark();
m_List.SetItemText(index,0,m_id);
m_List.SetItemText(index,1,m_Name);
m_List.SetItemText(index,2,m_Age);
m_List.SetItemText(index,3,m_DepartMent);
Try pumping some messages after updating the items.
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Turn off your sorting.
In your designer:
Properties>Behaviour>Sort set to None.
You have to add Item into 0 colomn index first.
listcontrol->InsertItem(0,_T("text"));
then, you can set text to the subItem;
listctrol->SetItemText(0,1,_T(subText)):
First, make sure the Owner Data property of the control is set to FALSE.
Maybe try m_List.Update(index) after the last SetItemText().
I must admit that everywhere I need updated list elements, I use an Owner Data CListCtrl because I think its faster in case of a big number of items and easier to handle in the long term.

How to get multiline tooltip for a list control on a dialog box?

As text on my list box is very huge I am trying to get multiline a tooltip on the list control.
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
mylist.EnableToolTips(TRUE);
mylist.SetExtendedStyle(LVS_EX_INFOTIP | mylist.GetExtendedStyle());
mylist.InsertColumn(0, L"suri", LVCFMT_LEFT, 10000);
CString str1 = L"nonNegativeInteger GetVehicleOwnerHolderByRegNumAndDateResponse.GetVehicleOwnerHolderByRegNumAndDateResult[optional].GetVehicleOwnerHolderByRegNumAndDateResultType.VHOwnerHolderResponse.VHOwnerHolderResponseType.Body.VehicleCountries.VehicleCountriesType.VehicleCountry[1, unbound].VehicleCountryType.VehCountryReplies.VehCountryRepliesType.VehCountryReply[1, unbound].Messages[optional].Message[1, unbound].MessageType.MessageCode";
for (int i = 0; i < 20 ; i++) {
CString str2;
str2.Format(L"%d",i);
str2 = str2 + str1;
mylist.InsertItem(LVIF_TEXT | LVIF_PARAM, i, str2, 0, 0, 0, NULL);
}
return TRUE; // return TRUE unless you set the focus to a control
}
I am getting following output which is truncated text i.e complete text is missing.
How to get text on tooltip multiline?
EDIT: I used following also.
still same result.
CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
if (pToolTip)
pToolTip->SetMaxTipWidth(SHRT_MAX);
You can get multi-line tooltips using newlines-charecters with SetMaxTipWidth() set to a large value. And if calling SetMaxTipWidth() with a small value, then it will automatically break into multiple lines when meeting a space-character.
You need to subclass your tooltip/infotip in order to use it:
BEGIN_MESSAGE_MAP(CListCtrl_InfoTip, CListCtrl)
ON_NOTIFY_REFLECT_EX(LVN_GETINFOTIP, OnGetInfoTip)
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolNeedText)
END_MESSAGE_MAP()
void CListCtrl_InfoTip::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
SetExtendedStyle(LVS_EX_INFOTIP | GetExtendedStyle());
}
BOOL CListCtrl_InfoTip::OnGetInfoTip(NMHDR* pNMHDR, LRESULT* pResult)
{
// Will only request tooltip for the label-column
NMLVGETINFOTIP* pInfoTip = (NMLVGETINFOTIP*)pNMHDR;
CString tooltip = GetToolTipText(pInfoTip->iItem, pInfoTip->iSubItem);
if (!tooltip.IsEmpty())
{
_tcsncpy(pInfoTip->pszText, static_cast<LPCTSTR>(tooltip), pInfoTip->cchTextMax);
}
return FALSE; // Let parent-dialog get chance
}
BOOL CListCtrl_InfoTip::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
...
// Break tooltip into multiple lines if it contains newlines (\r\n)
CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
if (pToolTip)
pToolTip->SetMaxTipWidth(SHRT_MAX);
...
}
There are two aspects to consider:
1. The size of the window
To activate multilines mode. This instruction is sufficient :
pToolTip->SetMaxTipWidth(SHRT_MAX);
2. Number of characters to display
For this second point it is necessary to be wary because the size of the field pszText is limited to 80 characters:
typedef struct tagNMTTDISPINFOA {
NMHDR hdr;
LPSTR lpszText;
char szText[80];
...
}
Therefore, even if you change SetMaxTipWidth you will not see any difference.
I suggest you to use the lpszText field which has no limit. Below is the code fragment that interests you:
pTTTW->lpszText = T2W (strTipText.GetBuffer (strTipText.GetLength ()));
Where strTipText is the CString that contains the message to show in the pop-up

How to Enable/Disable Menu Item 2 in OnUpdate Handler of Menu Item 1?

I have two menu items. When item 1 is disabled, I want item 2 to be disabled as well. In the OnUpdate handler of menu item 1, I have tried to use "t_pMenu = pCmdUI->m_pMenu;", "t_pMenu = pCmdUI->m_pSubMenu;" and "t_pMenu = pCmdUI->m_pParentMenu;" but I always get NULL t_pMenu. How can I achieve this purpose?
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
if(m_bShowMenuItem1)
{
pCmdUI->SetText("Hide Features")
CMenu * t_pMenu = pCmdUI->m_pSubMenu;
if(t_pMenu != NULL)
t_pMenu->EnableMenuItem(ID_MENU_ITEM2, MF_ENABLED);
}
else
{
pCmdUI->SetText("Show Features")
CMenu * t_pMenu = pCmdUI->m_pParentMenu;
if(t_pMenu != NULL)
t_pMenu->EnableMenuItem(ID_MENU_ITEM2, MF_GRAYED);
}
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
...
}
Never handle to different command IDs in one handler.
Each handler is called more than once if there are buttons and menu items. Also you don't know the sequence. When you alter the item2 in Item1 handler it may be enabled again when the handler for Item2 is called later.
When you have a flag named m_bShowMenuItem1 just use it.
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowMenuItem1);
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowMenuItem1);
}
I got it work. Below is the code I tried. Two flag variables m_bShowFeatures and m_bShowSmallFetures are initialized to be TRUE.
void CDummyView::OnMenuItem1()
{
m_bShowFeatures = !m_bShowFeatures;
m_pDoc->UpdateAllViews(NULL, SHOW_HIDE_ALL_FEATURES);
}
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
if(m_bShowFeatures)
pCmdUI->SetText("Hide Features")
else
pCmdUI->SetText("Show Features")
}
void CDummyView::OnMenuItem2()
{
m_bShowSmallFetures= !m_bShowSmallFetures;
m_pDoc->UpdateAllViews(NULL, SHOW_HIDE_SMALL_FEATURES);
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowFetures)
if(m_bShowSmallFetures)
pCmdUI->SetText("Hide Small Features")
else
pCmdUI->SetText("Show Small Features")
}
So OnUpdateMenuItem2() does get invoked after OnMenuItem1() is called when Menu Item 1 is clicked. I didn't expect that.

Handle Enter to change value of edit control in MFC

I use edit control to display input value in MFC. That value is used in a while loop to calculate other parameters. When using EN_CHANGE to update data value, I can not use float input number. How can I actually change that value after hit "Enter" key?
float m_ini_gainG; // m_ini_gainG is value of edit Control.
void CMy0Dlg::OnClickedPlay()
{
m_thread = AfxBeginThread(MainThread,this);
}
UINT CMy01Dlg::MainThread(LPVOID pParam)
{
CMy01Dlg *pMainDlg = (CMy01BayerToRGBDlg*)pParam;
while(1)
{
pMainDlg->m_iTimer = pMainDlg->SetTimer(DISPLAY, 10, 0);
}
return 0;
}
void CMy01BayerToRGBDlg::OnTimer(UINT_PTR nIDEvent)
{
if(nIDEvent == DISPLAY)
{
Read(data); // data is read continuously
......
gainB = temp_G*m_ini_gainG/temp_B;
m_gainB.Format(L"%0.2f",gainB);
UpdateData(FALSE);
}
CDialogEx::OnTimer(nIDEvent);
}
void CMy01BayerToRGBDlg::OnChangeIniG()
{
UpdateData(TRUE);
}
I can only set integer value for m_ini_gainG. I can not enter "." to set float value.