1

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

  • You could do custom data validation (DDV)/ data exchange (DDX). Or sometimes I use PreTranslateMessage to detect the invalid characters and suppress the message being processed. – Andrew Truckle Aug 04 '22 at 16:34
  • 2
    `PreTranslateMessage` will miss ways where an edit control's contents get changed that's not the result of pressing keys (e.g. copy-pasting). DDV is possible, but creates a horrible user experience (the enabled OK button suggests that everything were fine, and once you hit it an error message flies into your face). – IInspectable Aug 04 '22 at 16:40

2 Answers2

5

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.

Constantine Georgiou
  • 2,412
  • 1
  • 13
  • 17
  • thank you, super detailed and both examples are extremely helpful, quick followup : which would recommend under which scenarios, they seem to have the same results but is one ever "better" than the other ? – Husam Chekfa Aug 06 '22 at 22:17
1

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

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23
  • I'm not sure how you'd use a `CMFCMaskedEdit` control here. It has a different purpose, really, and is meant for input that follows a given pattern (such as dates). The mask supplied to the control also implies the length of text entered. I doubt this is a very tractable approach. – IInspectable Aug 04 '22 at 15:32
  • @SoronelHaetir thanks I am trying it out now, would I need to use any of the other functions found in the class other than ```SetValidChars``` ? – Husam Chekfa Aug 04 '22 at 17:43
  • @IInspectable could you elaborate what you mean by this ? this is my first time working with mfc so I didn't fully understand what you meant; is there another approach you would take ? – Husam Chekfa Aug 04 '22 at 17:44
  • @hus As I understand it, you have to opt-in to the control's features by calling [`EnableMask`](https://docs.microsoft.com/en-us/cpp/mfc/reference/cmfcmaskededit-class#enablemask). That requires that you pass in a template whose length matches the maximum length of the accepted input. Something you *could* work around, but it also doesn't provide an exclusion list (it only directly supports specifying valid characters). Again, something you *might* be able to work around by providing your own `IsMaskedChar` implementation. I genuinely wonder how you'd use this control to exclude certain chars. – IInspectable Aug 05 '22 at 08:54
  • In case you're looking for a sample, the [NewControls GitHub repo](https://github.com/microsoft/VCSamples/tree/master/VC2010Samples/MFC/Visual%20C%2B%2B%202008%20Feature%20Pack/NewControls) apparently contains `CMFCMaskedEdit` sample code (they live on [Page4](https://github.com/microsoft/VCSamples/blob/master/VC2010Samples/MFC/Visual%20C%2B%2B%202008%20Feature%20Pack/NewControls/Page4.cpp)). – IInspectable Aug 05 '22 at 08:55
  • Sorry, it's been two days without an answer to the question, **how** the proposed control could be used to implement the requested functionality. And while my personal take is that you simply can't, I'm going to have to down-vote. – IInspectable Aug 06 '22 at 09:08