C++ MFC - CEdit / EDITTEXT Control - only allow certain chars - c++

Thank you for the answers and comments. I chose the answer I chose because it allowed me to continue to use CEdit with just a couple of minor changes to the code. However, the solution considering CMFCMaskedEdit also seemed to work as well when tested. If you choose to use that solution make sure you apply the correct functions for the object such as SetValidChars etc upon initialisation ! :) Thank you again everyone
I am using Visual Studio Professional 2017 C++ with MFC
I have a CEdit object in my MFC project which also has an EDITTEXT control in my .rc file.
The CEdit object will be edited by the user who will type a keyword, and I will do something with that keyword, that is, find files that contain that keyword.
Naturally, due to my task, I cannot allow the following char s: \ / : * ? " < > | , since these chars are not allowed to be in a file or folder name.
What can I do to prevent a user from entering these characters into the CEditBox. Realistically, the only chars I will need are: A-Z, a-z, 0-9, and _.
Another specification: no regex please ! Ideally the answer will use a Control (I looked here) or function (I looked here) I may have overlooked.
If there is no solution, I will fall back to this:
I will check whether any of these chars are in the text the user entered. If no, awesome, nothing to worry about ! If yes, then I will return an error :)
Thank you in advance ! :D

I can think of two possible solutions to your question. The 1st solution posted just below is the easiest to implement because it does not require subclassing the control.
1st Solution - Control Notification
Edit controls send the EN_UPDATE notification, just before the (updated) text is about to be displayed. You can capture this event easily: open the Resource Editor, go to the dialog, select the edit conrol and in the Properties Editor go to Control Events page and Add the EN_UPDATE handler. The editor will add the handler to the message-map and generate the function:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
.
.
ON_EN_UPDATE(IDC_EDIT_FNAME, &(CMyDialog::OnEnUpdateEditFname)
END_MESSAGE_MAP()
In the generated function add the following code:
void CMyDialog::OnEnUpdateEditFname()
{
CString s;
GetDlgItemText(IDC_EDIT_FNAME, s); // Get the control's text - may contain illegal characters
// First illegal character position
int nFIChar = -1;
// Loop until all illegal chars are removed - will also work for a paste operation w/ multiple illegal chars
while (LPCTSTR p = _tcspbrk(s, _T("\\/:*?\"<>|")))
{
if (nFIChar<0) nFIChar = p-s; // Store 1st illegal char position
s.Remove(*p); // Remove illegal char(s)
}
if (nFIChar>=0) // At least one illegal char found
{ // Replace the control's text and display a balloon
CEdit *pEdit = (CEdit*)GetDlgItem(IDC_EDIT_FNAME);
pEdit->SetWindowText(s); // SetWindowText() will reset the caret position!
pEdit->SetSel(nFIChar, nFIChar); // Set caret to the 1st illegal character removed
MessageBeep(-1);
pEdit->ShowBalloonTip(NULL, _T("A file name can't contain any of the following characters:\n\t\\ / : * ? \" < > | "));
}
}
This will remove the illegal characters and will display a balloon tip, like when entering an illegal character while trying to rename a file in File Explorer. It's tested and works.
Alternative Solution - Subclassing
Another solution is possible, employing a subclassed control class:
Define a CEdit-derived class.
Add a handler for the WM_CHAR message.
In the WM_CHAR handler, if an illegal character is about to be entered, beep and display the balloon, but do NOT call the default, otherwise call it.
So the code could be:
BEGIN_MESSAGE_MAP(CFilenameEdit, CEdit)
ON_WM_CHAR()
END_MESSAGE_MAP()
void CFilenameEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (_tcschr(_T("\\/:*?\"<>|"), nChar))
{
MessageBeep(-1);
ShowBalloonTip(NULL, _T("A file name can't contain any of the following characters:\n\t\\ / : * ? \" < > | "));
}
else CEdit::OnChar(nChar, nRepCnt, nFlags);
}
You may want to add a handler for the WM_PASTE message too.
Then you have to use it in your dialog, just use the Class Wizard to add a member variable of the derived edit class, associated with the edit control. It can be easily reused in another project.
EDIT:
The 1st solution (capturing the EN_UPDATE notification) is easier to implement (although there's more code in this sample - the 2nd one doesn't currently handle the paste operations) because it does not require defining a new subclass. It's what a developer would choose to handle a special requirement, quickly implementing it for the project.
The 2nd solution defines a new subclass. It can be reused in another project - I tend to favor reusable code - but it needs to be completed (handle paste operations as well) and then maintained. And in order to be more useful it should preferably be enhanced, for example make it more general, like add an option for fully-qualified path/file names (they may contain \, : or ") or better yet allow the developer to define the set of invalid characters - in this case the message displayed should also be defined by the developer*, as the new class could be used in more cases, not just for filenames or paths. So this would require more work initially, and it's finally a matter of choice (a bigger "upfront investment", with potential future benefits).
* The 2nd line of the message, containing the invalid character list should be constructed programmatically, by the class's code
Note: The _tcspbrk() and _tcschr() (THCAR.H versions of strpbrk() and strchr()) are CRT functions. One could alternatively use the StrPBrk() or StrCSpn() and StrChr() functions from Shlwapi - many useful utility functions there btw.

I suggest you switch to using the CMFCMaskedEdit class instead of CEdit. It supports exactly the behavior you are after.

Related

MFC - Remove leading zeros for CEdit number control

I find that CEdit control has option 'Number' in its property, so that I can prevent user from enter non-digit character into this textbox - it is CEdit number control now.
If there is an option 'Number', I think maybe there is a way to remove leading zeros for CEdit which is just simple like option 'Number'.
I have tried Dialog Data Exchange with hope that it would remove leading zeros for me automatically, but it won't.
Then I think the way to do this is add EN_KILLFOCUS message for each of the CEdit number controls, but I find that exhausted.
So I think the better way to do that is add EN_KILLFOCUS, but all the CEdit number controls lose focus event point to one function, in this function I'll remove leading zero for the 'current' control, but in C# I can get the 'current' control, in C++ I don't know if it's supported.
Or inherit CEdit to make CEditNum - which implement lose focus remove leading zeros feature, but with this solution, I can't design it on the Visual Studio design window (I think). I hope there is a solution similar to this solution (which is a solution for Draw&Drop problem)
Anyway, before apply the final solution (EN_KILLFOCUS), I want to make sure if is there better way - least implement, reuse the existing implement of MFC.
A little explain about remove leading zeros: you enter: 00001 into the CEdit control, then lose focus, the CEdit control show you: 1. The idea is like MS Excel when you enter a number into its cell.
"but all the CEdit number controls lose focus event point to one function"
That is true, but you get the control ID of the control that's just lost focus as a parameter.
Add this to your Message table, replace IDC_FIRST, IDC_LAST with the first and last IDs of your edit controls, or use 0, 0xFFFFFFFF for all.
ON_CONTROL_RANGE(EN_KILLFOCUS, IDC_FIRST, IDC_LAST, OnKillFocus).
Here is the signature of OnKillFocus, and how to get a CWnd to apply changes.
void CMyDialogClass::OnKillFocus(UINT nID)
{
// you can further check if the ID is one of interest here...
// if your edit control control IDs are not contiguous, for example.
// you can get a CEdit* here, but only if you used DDX to map the
// control to a CEdit.
CWnd* pCtrl = GetDlgItem(nID);
if (pCtrl)
{
CString str;
pCtrl->GetWindowText(str);
// remove zeroes, or format as you like....
str.Format(_T("%d"), _tcstoi(str));
pCtrl->SetWindowText(str);
}
}
// if you mapped the control to a CEdit, here's how you can safely
// get a pointer to a CEDit
CEdit* pEdit = (CEdit*)GetDlgItem(nID);
ASSERT_KINDOF(CEdit, pEdit); // debug check
if (pEdit && pEdit->IsKindOf(RUNTIME_CLASS(CEdit))) // standard check
{
// ....
}

Simulate keyboard input inside app in C++ MFC app

I created a dialog MainDialog.cpp with 2 edit controls whose IDs are IDC_EDITCONTROL_A and IDC_EDITCONTROL_B, and have variables defined as m_editControlA and m_editControlB, respectively.
Also, I have 2 buttons whose IDs are IDC_MFCBUTTON_KEY_X and IDC_MFCBUTTON_KEY_Y, and variables are m_buttonKeyX and m_buttonKeyY, respectively.
Below is the code in the source file
#include "afxdialogex.h"
IMPLEMENT_DYNAMIC(CMainDialog, CDialogEx)
CMainDialog::CMainDialog(CWnd* pParent): CDialogEx(IDD_MAIN_DIALOG, pParent)
{
}
CMainDialog::~CMainDialog()
{
}
void CMainDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDITCONTROL_A, m_editControlA);
DDX_Control(pDX, IDC_EDITCONTROL_B, m_editControlB);
DDX(Control(pDX, IDC_MFCBUTTON_KEY_X, m_buttonKeyX);
DDX(Control(pDX, IDC_MFCBUTTON_KEY_Y, m_buttonKeyY);
}
BEGIN_MESSAGE_MAP(CMainDialog, CDialogEx)
ON_EN_CHANGE(IDC_EDITCONTROL, &CMainDialog::OnEnChangeEditA)
ON_BN_CLICKED(IDC_MFCBUTTON_KEY_X, &CMainDialog::OnBnClickedButtonX)
ON_BN_CLICKED(IDC_MFCBUTTON_KEY_Y, &CMainDialog::OnBnClickedButtonY)
END_MESSAGE_MAP()
void CMainDialog::OnBnClickedButtonX()
{
m_editControlA.SetWindowTextW(_T("X")); // test
}
void CMainDialog::OnBnClickedButtonX()
{
m_editControlA.SetWindowTextW(_T("Y")); // test
}
I am trying to understand how I can have each button send their respective character (i.e. X or Y in this example) to the selected edit control if one is selected. Essentially, I would like to simulate keyboard input.
I have read the docs about how to simulate keyboard events and also the sendMessage but I could not understand how to implement it since my C++ knowledge is very basic. Also, following my previous question I have found that the GetFocus would be useful but still my main issue currently is sending the input.
Any example code or useful link could be very useful for me to learn how I can simulate a keyboard input inside an app.
The characters are sent from the OS to the edit controls using the WM_CHAR message.
In reality it is a bit more complex than that, but you do not need to emulate the entire WM_KEYUP WM_KEYDOWN message sequence, since its end result is to generate a WM_CHAR message.
You can use CWnd::PostMessage to send characters directly to your edit controls, even when they do not have the focus.
You have probably already found the documentation for WM_CHAR here: https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms646276(v=vs.85).aspx
oops.. excuse my french, ths english doc is here
https://msdn.microsoft.com/en-us/library/windows/desktop/ms646276(v=vs.85).aspx
(just changing the fr-fr to en-us does the trick, it probably works for all other languages, neat!
wParam holds the character you want to send. Either an plain ASCII character, or one of the VK_ constants... I suggest you use the unicode version WM_CHARW, as most windows software uses unicode nowadays. The notation for wide chars is either L'X' or _T('X'), the unicode (UTF-16) character type is wchar_t.
lParam contains other keystroke details, 0 should be fine for what you want to do.
to send X, simply call
m_editControlA.PostMessage(WM_CHAR, _T('X'));
When using the _T() notation, the character (or string) literal between the parenthesis will be automatically converted to the right character width for your app's unicode setting (you should set that to UNICODE, since that's what the OS is using, and is also the only valid encoding for Windows CE, for example, and you should get used to manipulating this type.
the _T() macros and _t* overrides for almost all C library functions operating on strings are defined in tchar.h, which is included by Visual Studio in stdafx.h. Under MFC, you'll mostly use CString, but it's good to know where these things are.
[EDIT] When you get that running, you should start playing with WM_KEYDOWN. You will discover that PostMessage(WM_CHAR, VK_ESCAPE) directly to your dialog does not close it, while a PostMessage(WM_KEYDOWN, VK_ESCAPE) does. And that m_editBox.PostMessage(WM_KEYDOWN, _T('X')) will send a lower key 'x' to your edit box. But that's another topic to ivestigate.
Have fun with MFC!
For your last question:
Sure, but it gets a bit more complicated, as your button will gain focus, as soon as you click on it. You'd have to create handlers for EN_SETFOCUS for eeach of your edit boxes, and add a CWnd* data member to keep track of the last edit box that had focus.
Your EN_SETFOCUS handlers should look something like this
void CdlgDlg::OnEnSetfocusEdit1()
{
m_pWndLastFocus = &m_edit1;
}
Don't forget to set the pointer to NULL in your constructor and to chjeck it's valid before calling m_pWndLastFocus->PostMessage() though.
The way to synthesize input in MFC is by using the SendInput method.

MFC C++ CListBox get selected item

First let me say that I've been searching for a solution for couple of days now...
I'm trying to get selected item for ListBox. This is my code:
CListBox * pList1 = (CListBox *)GetDlgItem(IDC_LIST1);
CString ItemSelected;
// Get the name of the item selected in the Sample Tables list box
// and store it in the CString variable declared above
pList1->GetText(pList1->GetCurSel(), ItemSelected);
MessageBox(ItemSelected, "TEST", MB_OK);
Now when i try this i get an error message saying "The Parameter is incorect"
Your code looks OK except error handling. Also MessageBox parameters look incorrect. The first parameter should be of type HWND. I believe that this is the root cause of your problems. Use MFC standard AfxMessageBox instead:
CListBox * pList1 = (CListBox *)GetDlgItem(IDC_LIST1);
int nSel = pList1->GetCurSel();
if (nSel != LB_ERR)
{
CString ItemSelected;
pList1->GetText(nSel, ItemSelected);
AfxMessageBox(ItemSelected);
}
If the CListBox is in single selection mode, the CListBox::GetCurSel will return the selected index.
If the CListBox is in multi-selection mode, you should use CListBox::GetSelItems which will return a list of indices.
You cannot mix'n'match the functions.
And always check return codes (as others already wrote).
If You already have a data member MyList(of classCListBox) :
int nSel = MyList.GetCurSel();
CString ItemSelected;
if (nSel != LB_ERR)
{
MyList.GetText(nSel, ItemSelected);
}
CWnd class has a MessageBox function which does not need a HWND parameter. But yes, AfxMessageBox is a little bit more easier to use and can be called anywhere in the MFC code without having a CWnd-derived object. And a beside note: if call a WinAPI function inside MFC code (not needed here, but possible in other cases) it's good to prepend it with scope resolution operator in order to avoid any confusion, mistake and/or name conflict (e.g. ::MessageBox...).
One possible cause for "invalid parameter" error in OP code is that it uses an ANSI string literal ("TEST") in a UNICODE build configuration. This case, must use an UNICODE string literal (L"TEST") or a little bit better, use _T macro (_T("TEST")) that makes it possible to build in both ANSI and UNICODE configurations.

MFC C++ VS 2010 : Edit Box to accept only alphabets, backspace and spaces

As mentioned in the title, I am currently using VS 2010 C++ , MFC application for my project. Currently new to programming.
I am currently asked to create an edit box to accept names, full names, e.g "Lee Roy Long". I have looked through many other websites but I am confused with which method should I use to do it.
Is there any examples or a guide to how to go about this?
EDIT: I have another question aside from this solved one [ Cannot Post new questions due to the "restrictions"], I am currently using the same edit box to add new names as strings into the SQLite database. I am currently having some trouble converting CString to string
vector<int> userSerialNumber;
vector<string> userName;
vector<int> userID;
vector<int> userTrainingImagesNo;
Program starts here:
CString str,text;
CString Lone = _T("MEEP"); // This one converts it succesffuly...
string ss((CStringA(Lone)));/Only works for declared CStrings?
CEdit* editBox = (CEdit*)GetDlgItem(IDC_EDIT1);
editBox->GetWindowText(str);
Adding the user's input from above into the program below.
userSerialNumber.push_back(newserialnumber);
userID.push_back(newserialnumber);
userName.push_back(ss);
userTrainingImagesNo.push_back(Img);
I have referred to many websites on how to convert CStrings to strings, but none of them worked, including this one.
As I debug the program, the conversion between CString and string did not work as I get "" for string, which causes the database to update a blank "".
CString str = "name";//Name CString gotten from EditBox
std::string newname = ""; //After typing many conversion methods, results ""
Is there something that I did not notice regarding this ?
You can filter the keystrokes going into the edit control by deriving a class from CEdit and handling the WM_CHAR message in your derived class. To accept a key pass it along to CEdit::OnChar, to reject a key simply return without calling the CEdit function.
To connect the edit control to your code you use a standard MFC subclassing technique. Right-click on the control and create a control member variable (a CEdit) in the parent window. Then edit to change the variable from a CEdit to a CYourDerivedCEdit.
There is a tutorial about this and a sample project at http://www.flounder.com/validating_edit_control.htm
As an alternative to trapping each character, you can handle the CWnd::OnKillFocus event for the edit box and interrogate the value once. Validating can be done by using CString::SpanExcluding with numbers and any other character that should not be in the resulting string. For example,
CString stringEnteredByUser = _T("Lee Roy Long");
CString validatedString = stringEnteredByUser.SpanExcluding("0123456789");
if (stringEnteredByUser != validatedString)
AfxMessageBox(_T("Invalid string"), MB_OK);
The 'stringEnteredByUser' variable should contain the string entered by the user. In this example, using SpanExcluding will tell you if they've entered a number. The returned string from the call (validatedString) will not match the string the user typed (stringEnteredByUser) if they've entered a character that is invalid (ie. the character is within the list provided to the SpanExluding call).
If the validatoin fails, simply force the focus back to the edit box.
I'm assuming you know some basic event coding.
Use the Textbox.textchanged event.
Also research ASCII and its conversion (asc function.)
If you need any more help, comment below.
Good luck!

How to disable Edit control's focus on Dialog first launch?

Hello everybody reading this. Thanks in advance for your time.
One thing before question: I DO NOT use neither MFC nor Windows Forms, just WinApi in C++.
Well, I am making a polynomial calculator in Visual C++. I added a Dialog to it, which was created in resources (.rc file) using drag'n'drop method. I suppose there would be no such a problem if i created my Dialog with CreateWindowEx (but I don't want to).
My Dialog has a few of Edit Controls. Everything is fine except that when the Dialog is launched, one of Edit controls takes focus to be ready to take keyboard input.
I have included management of EN_KILLFOCUS (Edit sends it to parent when loses focus due to selecting another control).
Here I read from control to wstring (string of wide characters - _UNICODE is defined), use some kind of parser to verify this wstring and remove bad characters, and then put correct string into the same edit control. It works fine, but here is the source of my problem:
When there was no input, parser returns string "0" (not the NULL, string is just set to "0"), as if control had focus and then lost it even before I clicked anything in Dialog.
Due to that, and something else (this is what I have to figure out), at the Dialog launch parser puts this string "0" to edit.
I want to make my edit not be able to take input from keyboard until i click one of the Edits (including this one).
If it is not possible, I want to clear the whole text at the beginning of dialog (being able to take input is not a problem, I just want to prevent parser from entering string "0" at the beginning)
My code:
In DlgProc I have:
//up here is switch to manage all controls
case MyEditID: // here is ID of one of my edits from resources
switch (HIWORD(wParam))
{
case EN_KILLFOCUS: // edit lost focus - another control selected
if (LOWORD(wParam)==MyEditID) //necessary to determine if
// one of allowed Edits sent this message
// because I have also other Edits
{
GetDlgItemText(hPanel, LOWORD(wParam), MyTempWcharArray, 100);
MyTempString.assign(MyTempWcharArray);
w1 = polynomial(MyTempWcharArray); // parser takes the string
// and removes bad chars in constructor
// polynomial is my class - you don't have to care of it
// w1 is declared before as object of polynomial class
MyTempString = w1.ConversionToString();
SetDlgItemText(hDialog, LOWORD(wParam), sw1);
}
break;
}
break;
does it matter what integer number is set to Edit's ID?
I know SetFocus(), and WM_SETFOCUS message. In this case I just can't get this working.
If i haven't included something important to make you see my point please let me know. I'm sorry I'm just a newbie in WinAPI world.
EDIT:
For those with a similar problem: Do not do this:
I made an workaround with global variable ProcessKillFocus set to false indicating that instructions in message management should not be processed, except that at the end (just before break;) I am changing it to true, so next time and later it will be processed:
case EN_KILLFOCUS:
if (ProcessKillFocus && LOWORD(wParam)==MyEditID)
{
// first time global ProcessKillFocus is false so all this is skipped
// 2nd time and later do all the stuff
}
ProcessKillFocus = true;
break;
Huge thanx to Sheyros Adikari for making my question easy to understand!!!
Huge thanx to patriiice for simple answer on a huge messing question!!!
ANSWER:
BTW: patriiice, I tried this:
case WM_INITDIALOG:
SetFocus(GetDlgItem(hDialog, Desired_Control_ID));
return (INT_PTR)FALSE;
break;
IT JUST WORKS!!!
You have to return FALSE to WM_INITDIALOG message and set the correct focus by yourself.