02
Aug
2007

Introducere

De multe ori este necesar intr-o aplicatie un control edit care sa permita introducerea doar a unui anumit set de caractere. Din fericire web-ul e plin de astfel de implementari, dar in general acestea au un mare neajuns: permit introducerea de caractere dinafara setului sau formatului prin operatia de copy/paste. Acest articol prezinta o astfel de implementare (cu support pentru Unicode) in care problema copy/paste-ului este rezolvata.

Implementarea clasei CSpecialEdit

Primul lucru ce trebuie facut e sa derivam clasa CEdit si suprascrie OnChar() pentru a filtra caracterele:

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);
}

In primul rand trebuie asigurat ca operatiile de copy/cut/paste/undo nu sunt ignorate. Apoi se face verificarea datelor introduse si sunt ignorate cele care nu corespund criteriului.

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);
}

Caracterul backspace trebuie permis pentru a putea sterge textul, iar Enter pentru a edita text in controale edit pe mai multe linii.

m_formatter este un obiect de tipul IFormat. Aceasta este o clasa abstracta care expune o metoda numita IsCharAllowed() care ia ca parametru un caracter si returneaza true daca este in setul permis si false in caz contrar.

Pentru a ne asigura ca utilizatorii nu pot face paste la text cu caractere nepermise, mesajul WM_PASTE trebuie tratat:

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);
}

Am suprascris metoda WindowProc si am verificat continutul clipboard-ului atunci cand WM_PASTE este receptionat. Daca clipboardul contine text cu caractere nepermise, mesajul nu mai este directionat mai departe, iar operatia paste este ignorata.

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;
}

Clasa abstracta IFormat e derivata de BaseFormat, dar care nu poate fi instantiate intrucat are acces protected la constructor si destructor.

class BaseFormat : public IFormat
{
   std::vector m_listChars;

protected:
   BaseFormat();
   virtual ~BaseFormat();
public:

   void SetAllowedChars(std::vector chars);
   void SetAllowedChars(LPCTSTR chars, int size);

   virtual bool IsCharAllowed(TCHAR nChar);
};

Aceasta clasa ofera doua metode de definire a setului de caractere permis, si implementeaza metoda IsCharAllowed din IFormat. Ea este derivata de o serie de clase, BinaryFormat, OctalFormat, DecimalFormat, HexFormat, care definesc setul de caractere permis (corespunzand bazei numerice pe care o reprezinta). De exemplu constructorul pentru HexFormat arata astfel:

HexFormat::HexFormat()
{
   LPCTSTR format = _T("0123456789ABCDEFabcdef");
   SetAllowedChars(format, _tcslen(format));
}

Pe langa acestea mai exista CustomFormat, care permite definirea oricarui set de caractere dorit:

class CustomFormat : public BaseFormat
{
public:
   CustomFormat(std::vector chars);
   CustomFormat(LPCTSTR chars, int size);
   virtual ~CustomFormat();
};

Clasa CSpecialEdit contine un pointer la un obiect de tipul IFormat, si are o metoda pentru a seta acest pointer.

void SetFormatter(IFormat *formatter);

Prin aceasta abordare este foarte usoara crearea de noi seturi de caractere permise prin derivarea din BaseFormat.

Folosirea lui CSpecialEdit

Primul pas in folosirea acestei clase este includerea header-ului sepcialedit.h:

#include "SpecialEdit.h"

Apoi declarati o variabila de tip CSpecialEdit si una de tipul IFormat* (cocul de mai jos este luat din aplicatia demo atasata):

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 functia DoDataExchange() adaugati o intrare pentru fiecare 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
}

Apoi trebuie creata o instanta la una din clasele derivate din IFormat si setarea pointerului catre acest obiect pentru fiecare control in parte:

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);
}

Nu uitati sa stergeti aceste obiecte in momentul in care nu mai sunt folosite. Pentru mai multe informatii vedeti aplicatia demo atasata.