title: Input validation of a subclassed edit control to only accept floating point numbers
body: <p>On my current project I have been struggling the last couple of days with subclassing edit boxes. By now I successfully subclassed my edit box and validated the input such that it only accepts digits, commas, a minus sign and keyboard commands.</p>
  
  <p>But for quite a while I'm now stuck with the refinement of the input validation. I want my edit box to behave as the following:</p>
  
  <ul>
  <li>accept minus sign only at first position</li>
  <li>accept only one leading zero</li>
  <li>accept only one comma</li>
  <li>force comma after leading zero</li>
  <li>manage those cases when deleting single characters or selections of the text via 'back', 'delete', select-all and then pasting something over it</li>
  </ul>
  
  <p>My code in its current form looks like this and provides almost none of the advanced validation requirements I specified above:</p>
  
  <pre><code>inline LRESULT CALLBACK decEditBoxProc(HWND hWnd,
                                         UINT msg, 
                                         WPARAM wParam, 
                                         LPARAM lParam,
                                         UINT_PTR uIdSubclass,
                                         DWORD_PTR dwRefData)
  {
      if(msg == WM_CHAR)
      {
          decEditBoxData* data = reinterpret_cast&lt;decEditBoxData*&gt;(ULongToPtr(dwRefData));
  
          bool isDigit          = (wParam &gt;= '0' &amp;&amp; wParam &lt;= '9');
          bool isZero           = ((wParam == '0') &amp;&amp; !data-&gt;blockZero);
          bool isSign           = (wParam == '-');
          bool isComma          = ((wParam == '.' || wParam == ',') &amp;&amp; !data-&gt;blockComma);
          bool isValidCommand   = (wParam == VK_RETURN  
                                  || wParam == VK_DELETE 
                                  || wParam == VK_BACK);
  
  
          // Restrict comma to one.
          if(isComma &amp;&amp; data-&gt;nCommas &gt; 0)
              return FALSE;
          else if(isComma &amp;&amp; data-&gt;nCommas == 0)
              data-&gt;nCommas++;
  
          // Restrict trailing zeroes to one.
          if(isZero &amp;&amp; data-&gt;nTrailingZeroes &gt; 0)
              return FALSE;
          else if(isZero &amp;&amp; data-&gt;nTrailingZeroes == 0)
              data-&gt;nTrailingZeroes++;
  
          // Filter everything but digits, commas and valid commands.
          if(!isDigit &amp;&amp; !isValidCommand &amp;&amp; !isComma)
              return FALSE;
      }
      return DefSubclassProc(hWnd, msg, wParam, lParam);
  }
  </code></pre>
  
  <p>Any idea on how to algorithmically solve this problem is very appreciated.</p>
  
  <h2>UPDATE</h2>
  
  <p>Thanks to the suggestions of David Heffernan and IInspectable I was able to (almost) solve my problem without subclassing the edit controls.</p>
  
  <p>In the dialog procedure (thats contains the edit controls):</p>
  
  <pre><code>switch(msg)
  {
  case WM_COMMAND:
     switch(LOWORD(wParam))
     {
        case IDC_IN_REAL:
           if(HIWORD(wParam)==EN_CHANGE) onEditChange(hDlg, IDC_IN_REAL);
           break;
  
        case IDC_IN_IMAG:
           if(HIWORD(wParam)==EN_CHANGE) onEditChange(hDlg, IDC_IN_IMAG);
           break;
      }
      break;
  }
  </code></pre>
  
  <p>With onEditChange:</p>
  
  <pre><code>void onEditChange(HWND hDlg, int ctrlID)
  {
      HWND hEdit    = GetDlgItem(hDlg, ctrlID);
      size_t len    = GetWindowTextLength(hEdit)+1;
      wchar_t* cstr = new wchar_t[len];
      GetWindowText(hEdit, cstr, len);
  
      std::wstring wstr(cstr);
  
      if(!(tools::isFloat(wstr)))
      {
          EDITBALLOONTIP bln;
          bln.cbStruct = sizeof(EDITBALLOONTIP);
          bln.pszTitle = L"Error";
          bln.pszText  = L"Not a valid floating point character.\nUse '.' instead of ','";
          bln.ttiIcon  = TTI_ERROR;
          Edit_ShowBalloonTip(hEdit, &amp;bln);
      }
      delete [] cstr;
  }
  </code></pre>
  
  <p>and isFloat():</p>
  
  <pre><code>bool tools::isFloat(std::wstring str)
  {
      std::wistringstream iss(str);
      float f;
      wchar_t wc;
      if(!(iss &gt;&gt; f) || iss.get(wc))
          return false;
      return true;
  }
  </code></pre>
  
  <p>I will probably add some more visual feedback for the user, but that's not important right now.</p>
  
  <p>The question however, is not answered yet. My intention was to allow the "," as a possible decimal point.</p>