c++user-interfacemfccontrolscedit

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


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


Solution

  • 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:

    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.