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.



About the Author

Marius Bancila

Marius Bancila is a Microsoft MVP for VC++. He works as a software developer for a Norwegian-based company. He is mainly focused on building desktop applications with MFC and VC#. He keeps a blog at www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++/VC++ programmers.

Downloads

Comments

  • Getting Error

    Posted by computermarshal on 03/19/2010 10:08am

    The 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

    • RE: Getting Error

      Posted by cilu on 03/19/2010 05:10pm

      I 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.

      Reply
    Reply
  • Nicely designed

    Posted by kirants on 08/18/2005 07:29pm

    very easy to use, and very well structured/designed. Your idea/code just became a part of a product ;)

    Reply
  • Great help for lots of applications

    Posted by JonnyPoet on 07/29/2005 06:07pm

    This 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

    Reply
  • How to accept UNICODE character input without compiling the problem with UNICODE directive

    Posted by ryu on 05/26/2005 01:19am

    Hi 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...

    • re: unicode

      Posted by ryu on 10/22/2005 12:49am

      Thank you for your nice reply, Gunas01.
      
      Of course, I know about UNICODE. My application is an old application, written 10 years ago. No UNICODE has been even considered by those programmers who wrote this application. Therefore, if you try to compile using UNICODE, you must change a lot in the code.
      
      However, I am just wondering, do you know anything about sub-classing? I bet you that you have no idea what I was asking in this forum. I also think that you don't know what you are talking about.
      
      Prove it...!!!
      
      Write a very simple code that could accept Japanese, Chinese, Korean, and Vietnamese characters from English machine (without UNICODE directive compilation). Then display it in English machine.
      
      I am challenging you not because I am offended with your comment, but I am challenging your "comment". I want to know whether I should listen to you or I should just ignore you (because you just being a smartass).
      
      Cheers...

      Reply
    • unicode

      Posted by gunas01 on 08/14/2005 05:19am

      Congratulations 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!

      Reply
    • re: unicode

      Posted by ryu on 07/03/2005 12:18am

      Hi 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 :)

      Reply
    • re: unicode

      Posted by ryu on 05/26/2005 08:55pm

      Hi Marius,
      
      Thanks for the quick response.
      
      It will be impossible for me to compile in UNICODE since there will be too much changes to be made. Thanks though.
      
      Cheers...

      Reply
    • re: unicode

      Posted by cilu on 05/26/2005 03:13am

      Well, you have to compile with UNICODE if you want support for unicode chars...

      Reply
    Reply
  • Good Thinking!....

    Posted by danandu on 05/19/2005 02:41am

    hi,
    this is dan from bangalore.
    u have a goog visualization. i like ur way of thinking. i would like to have more discussion with u.
    well done.

    Reply
  • Quite interesting...

    Posted by ovidiucucu on 05/13/2005 03:49am

    ...the ideea. :D

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Live Webinar Tuesday, August 26, 2014 1:00 PM EDT Customers are more empowered and connected than ever, and the customer's journey has grown more complex. Their expectations are growing and trust is diminishing as they may interact with multiple brands through web, mobile and social channels. Considering 70% of the buying process in a complex sale is already complete before prospects are willing to engage with a live salesperson -- it's critical to understand your customers and anticipate their needs.* …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds