Customizable Masked Edit Control
Introduction
Did you ever need an edit control that was allowed to accept only special characters; for instance, only hexadecimal chars and nothing else? That's not that hard to write, and there are plenty of masked controls on the Web that basically do that one way or another, but most of them have a big flaw: You can paste undesired characters in the control. That was something I could not allow in a recent project that I worked on, and I wanted to share what I did to avoid it with those who need something similar. The control presented in this article is designed to support UNICODE characters.
CSpecialEdit Class Implementation
The way to do this is to derive CEdit and override OnChar(), filtering only the desired chars.
void CSpecialEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(GetKeyState(VK_CONTROL) & 0x80000000)
{
switch(nChar)
{
case 0x03:
Copy();
return;
case 0x16:
Paste();
return;
case 0x18:
Cut();
return;
case 0x1a:
Undo();
return;
}
}
if(!IsCharAllowed(nChar))
{
MessageBeep(-1);
return;
}
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
First, you must ensure that the Copy/Cut/Paste/Undo operations done with the keyboard are not also discarded. Then, you check the validity of the input and disregard unwanted characters.
bool CSpecialEdit::IsCharAllowed(TCHAR nChar)
{
switch(nChar)
{
case _T('\b'):
case 10:
case 13:
return true;
}
ASSERT(m_formatter != NULL);
return m_formatter->IsCharAllowed(nChar);
}
The backspace key should be allowed to delete characters and the Enter key to use multi-line edits.
m_formatter is an object of type IFormat, an abstract base class that exposes a IsCharAllowed() method that takes a char as input and returns true if the char is in the allowed set.
To make sure users cannot paste text with prohibited characters, WM_PASTE must be handled:
LRESULT CSpecialEdit::WindowProc(UINT message, WPARAM wParam,
LPARAM lParam)
{
switch(message)
{
case WM_PASTE:
if(!IsClipboardOK())
{
MessageBeep(-1);
return 0;
}
}
return CEdit::WindowProc(message, wParam, lParam);
}
I have overridden the WindowProc() and checked the Clipboard content when a WM_PASTE is received. If the Clipboard contains text with disallowed characters, the message is not further dispatched and the paste operation is canceled.
bool CSpecialEdit::IsClipboardOK()
{
bool isOK = true;
COleDataObject obj;
if (obj.AttachClipboard())
{
HGLOBAL hmem = NULL;
TCHAR *pUniText = NULL;
DWORD dwLen = 0;
bool bText = false;
if (obj.IsDataAvailable(CF_TEXT))
{
hmem = obj.GetGlobalData(CF_TEXT);
char *pCharText = (char*)::GlobalLock(hmem);
#ifdef UNICODE
int lenA = strlen(pCharText);
int lenW = MultiByteToWideChar(CP_ACP, 0, pCharText, lenA,
0, 0);
if (lenW > 0)
{
pUniText = ::SysAllocStringLen(0, lenW);
MultiByteToWideChar(CP_ACP, 0, pCharText, lenA,
pUniText, lenW);
bText = true;
}
else
{
::GlobalUnlock(hmem);
return false;
}
#else
pUniText = pCharText;
#endif
}
#ifdef UNICODE
else if(obj.IsDataAvailable(CF_UNICODETEXT))
{
hmem = obj.GetGlobalData(CF_UNICODETEXT);
pUniText = (TCHAR*)::GlobalLock(hmem);
}
#endif
if(hmem)
{
DWORD dwLen = _tcslen(pUniText);
for(DWORD i=0; i<dwLen && isOK; i++)
isOK = IsCharAllowed(pUniText[i]);
::GlobalUnlock(hmem);
#ifdef UNICODE
if(bText)
::SysFreeString(pUniText);
#endif
}
else
return false;
}
return isOK;
}
The IFormat abstract class is derived by the BaseFormat class that cannot be instantiated because it has protected constructors and a destructor.
class BaseFormat : public IFormat
{
std::vector<TCHAR> m_listChars;
protected:
BaseFormat();
virtual ~BaseFormat();
public:
void SetAllowedChars(std::vector<TCHAR> chars);
void SetAllowedChars(LPCTSTR chars, int size);
virtual bool IsCharAllowed(TCHAR nChar);
};
It provides an overloaded method for setting the allowed char set and implements IsCharAllowed() from IFormat.
The base class is derived by a series of BinaryFormat, OctalFormat, DecimalFormat, and HexFormat classes that defines the appropriate allowed char set. Here is the example for HexFormat.
HexFormat::HexFormat()
{
LPCTSTR format = _T("0123456789ABCDEFabcdef");
SetAllowedChars(format, _tcslen(format));
}
In addition, there is a CustomFormat class derived from BaseFormat that can define a custom allowed char set.
class CustomFormat : public BaseFormat
{
public:
CustomFormat(std::vector<TCHAR> chars);
CustomFormat(LPCTSTR chars, int size);
virtual ~CustomFormat();
};
The CSpecialEdit class has a pointer to an IFormat object and provides a method to set this pointer to an IFormat object.
void SetFormatter(IFormat *formatter);
By using this approach, it is very easy to create new specific masks by deriving a BaseFormat class; for instance, create a format mask to allow only uppercase letters.
Using the CSpecialEdit Control
Using the CSpecialEdit is very simple. First, you must include a header in your dialog class header:
#include "SpecialEdit.h"
Then, declare a variable of type CSpecialEdit and also a variable of type IFormat* (the following code is taken from the attached demo project where four special edits are used):
CSpecialEdit m_editBinary; CSpecialEdit m_editCustom; CSpecialEdit m_editDecimals; CSpecialEdit m_editHex; IFormat *m_pBinaryFormat; IFormat *m_pDecimalFormat; IFormat *m_pHexFormat; IFormat *m_pCustomFormat;
In DoDataExchange(), add a call to DDX_Control for each control:
void CSpecialEditsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSpecialEditsDlg)
DDX_Control(pDX, IDC_EDIT_BINARY, m_editBinary);
DDX_Control(pDX, IDC_EDIT_CUSTOM, m_editCustom);
DDX_Control(pDX, IDC_EDIT_DECIMALS, m_editDecimals);
DDX_Control(pDX, IDC_EDIT_HEX, m_editHex);
//}}AFX_DATA_MAP
}
The only thing left to do is to create instances of IFormat-derived classes and set references to them for the edit controls:
void CSpecialEditsDlg::InitEditControls()
{
m_pBinaryFormat = new BinaryFormat;
m_pDecimalFormat = new DecimalFormat;
m_pHexFormat = new HexFormat;
m_editBinary.SetFormatter(m_pBinaryFormat);
m_editDecimals.SetFormatter(m_pDecimalFormat);
m_editHex.SetFormatter(m_pHexFormat);
}
For more information (how to use CustomFormat), see the code of the demo project.

Comments
Getting Error
Posted by computermarshal on 03/19/2010 10:08amThe code is awesome but i only want the decimal format so i included BaseFormat DecimalFormat IFormat SpecialEdit in my dialog based application. I did the same as you mentioned in your article section "Using the CSpecialEdit Control". the code compiled successfully but when i run it it gives me error Debug Assertion Failed! File: SpecialEdit.cpp Line: 50
-
ReplyRE: Getting Error
Posted by cilu on 03/19/2010 05:10pmI suggest some debugging. You'll notice that at line 50 there is an assert: ASSERT(m_formatter != NULL); Since this fails it means the formatter is NULL. That means you forgot to call SetFormatter passing a valid pointer. Please check your code before posting that you get errors.
ReplyNicely designed
Posted by kirants on 08/18/2005 07:29pmvery easy to use, and very well structured/designed. Your idea/code just became a part of a product ;)
ReplyGreat help for lots of applications
Posted by JonnyPoet on 07/29/2005 06:07pmThis is really an often upcoming problem easily handled and could very easy attached to my own programs. I only will add a date format for my purposes
ReplyHow to accept UNICODE character input without compiling the problem with UNICODE directive
Posted by ryu on 05/26/2005 01:19amHi Marius, I am able to display the UNICODE character in CEdit control, but I am having the trouble to accept UNICODE input from the keyboard to CEdit control. When I typed in some UNICODE character, it automatically convert it to question mark ("?"). Any idea how can I accepting UNICODE input without compiling the application using UNICODE directive? Thanks...
-
Reply
-
-
-
Reply
-
Replyre: unicode
Posted by ryu on 10/22/2005 12:49amunicode
Posted by gunas01 on 08/14/2005 05:19amCongratulations Samin! It took you 1.5 months to figure that one out. Of course, it's because you didn't really understand that UNICODE is a character encoding scheme and not really the "foreign characters" itself. And yes, it'll still work without UNICODE, many people has written programs like that in Windows 9X. Cheers!
Replyre: unicode
Posted by ryu on 07/03/2005 12:18amHi Marius, I have been successfully sub-classing the CEdit class. It allows my application to accept UNICODE characters from the keyboard without re-compiling my application with UNICODE directive. Cheers :)
Replyre: unicode
Posted by ryu on 05/26/2005 08:55pmre: unicode
Posted by cilu on 05/26/2005 03:13amWell, you have to compile with UNICODE if you want support for unicode chars...
ReplyGood Thinking!....
Posted by danandu on 05/19/2005 02:41amQuite interesting...
Posted by ovidiucucu on 05/13/2005 03:49am...the ideea. :D
Reply