Jump to content




Recent Status Updates

  • Photo
      30 Sep
    rhossis

    laptop hard disk seated beneath motherboard but with no access panel. 7 hours to replace :(

    Show comments (3)
  • Photo
      19 Sep
    Chall

    I love it when you go to write a help thread, then while writing, you reach an enlightenment, and figure it out yourself.

    Show comments (3)
View All Updates

Developed by Kemal Taskin
Photo
- - - - -

Number Only TEdit

tedit number-only es_number windows api gui format

  • Please log in to reply
No replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1,320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 13 October 2012 - 10:50 PM

Overview

I believe it's not a mistake if I say that TEdit is one of the most frequently used VCL control (of course after TLabel). It is used to display text values and to receive text values from users.

In many occasion we would like to only receive numbers input, any other characters must not acceptable. Of course we could validate the inputted values after user fully completed data input (e.g. by clicking OK button). However it would be less user friendly since might already entered big volume of data before realize that some data was in "bad" format. The best is we can validate the data at the same time when the user entering them. Hence the need for TEdit that receive only numbers.


ES_NUMBER Window Style

Since TEdit is actually a wrapper to Windows text editor control, the first location to look for answer is Windows API for creating window controls, i.e. CreateWindow. Visit this MSDN page for official information about CreateWindow.

In the CreateWindow MSDN page, if you look up for more information on control styles of EDIT class you would find an entry named ES_NUMBER. Below I quote the explanation of it.

ES_NUMBER

Allows only digits to be entered into the edit control. Note that, even with this set, it is still possible to paste non-digits into the edit control.
To change this style after the control has been created, use SetWindowLong.
To translate text that was entered into the edit control to an integer value, use the GetDlgItemInt function. To set the text of the edit control to the string representation of a specified integer, use the SetDlgItemInt function.


Don't you think this is interesting? Since TEdit is a wrapper of Windows EDIT class control, we can apply ES_NUMBER style to a TEdit instance to make it only receive digit chars. For example, if you have a TEdit control named Edit1 in a form, then you can add the following code in the form's OnCreate event.

  SetWindowLong(Edit1.Handle
                , GWL_STYLE
                , GetWindowLong(Edit1.Handle, GWL_STYLE) or ES_NUMBER
               );

However there is a catch. The quoted information already mentioned it, although not thorough. In Windows versions prior to Vista, TEdit with ES_NUMBER style is still be able to receive non-digit chars from clipboard (i.e. through paste operation). So users still can CTRL+V or right click in the TEdit to show a popup menu and select Paste to put in any kind of text.

This problem is not present in Windows Vista and 7. This is why personally I consider it was a bug. Not to mention that earlier documentation of Windows API never mentioned this problem. I quote the explanation of ES_NUMBER style in Windows 32 SDK that came with Delphi 7.

ES_NUMBER Allows only digits to be entered into the edit control.


So if your application(s) is targeting Windows Vista, 7, and newer ones, then implementation of ES_NUMBER style has perfectly solved the problem. But if you still target earlier Windows, please read on.


NumbersOnly Property of TEdit in newer Delphi Versions

In newer Delphi versions, TEdit has NumbersOnly property. I lost track since which version of Delphi this property existed. I think it starts from Delphi 2010. But the property was definitely there in Delphi XE2.

Although I did not inspect the real code behind this property, but judging from the runtime behaviour I concluded that this property is relying on ES_NUMBER style. Therefore it has the same problem with ES_NUMBER implementation in earlier Windows.

Again, if you have newer Delphi which TEdit has NumbersOnly property, and you are targeting Windows Vista or newer, then you are good to go. This tutorial would not help you anymore. But if you still targetting earlier Windows versions, then read on.


Override the Handling of WM_PASTE Message

So far we only able to validate chars that were typed in by users. Now let's handle when the chars were inputted through pasting operation. That means we have to override the handling of WM_PASTE message by our TEdit.

Receiving WM_PASTE message means that there is a pasting operation initiated on the receiver window control. For our case, we want to inspect the content of the clipboard which about to be pasted to see if it's numbers only of not. If it's not numbers only, we simply raise an exception to prevent the pasting process from being completed. When clipboard content is numbers only, we simply allow the pasting process to proceed as usual.

Had you read my previous tutorial of Creating Column Events for TListView, you would already be familiar with the technique we will use here. I.e. by replacing the WindowProc property of our TEdit(s). So if in your form you have a TEdit with name of Edit1 which you don't want to accept pasting of teks containing non-number characters, you could use the following codes.

interface
  ...
  ...
types
  TForm1 = class(TForm)
    ...
    Edit1: TEdit;
    ...
    procedure FormCreate(Sender: TObject);
  private
    FOldEditWndProc: TWndMethod;
    procedure CustomWndProc(var AMsg: TMessage);
  end;

  ...
  ...

implementation

...
...
procedure TForm1.CustomWndProc(var AMsg: TMessage);
var
  S: string;
  i: Integer;
begin
  // handle pasting message only
  if AMsg.Msg=WM_PASTE then
  begin
    // Get text version of clipboard content
    S := Clipboard.AsText;
    // check if the content of the clipboard convertible to integer, which means
    // the content is number(s) only
    if (S <> '') and not TryStrToInt(S, i) then
      // Raise EUnacceptableChars exception
      raise EUnacceptableChars.Create('Unacceptable character(s)');
  end;

  // Call the original message handler
  FOldEditWndProc(AMsg);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // set the ES_NUMBER "number only" style
  SetWindowLong(Edit1.Handle
                , GWL_STYLE
                , GetWindowLong(Edit1.Handle, GWL_STYLE) or ES_NUMBER
               );

  {$IFDEF Unicode}
  // Unicode Delphi programs only need to subclass WndProc (for custom handling
  // pasting operation) when running in in Windows prior to Vista
  if not IsVistaOrAbove then
  begin
    FOldEditWndProc := Edit1.WindowProc;
    Edit1.WindowProc := CustomWndProc;
  end;
  {$ELSE}
  // Non-unicode Delphi programs always, so far, need the subclasing
  FOldEditWndProc := Edit1.WindowProc;
  Edit1.WindowProc := CustomWndProc;
  {$ENDIF}
end;
...
...


Demo Project

Let's create a demo project to see if what we have obtained so far would successfully applicable. For this tutorial we would need to have access to unicode and non-unicode Delphi, in order to see any difference(s).

In non-unicode Delphi.

GUI
  • Create a new project and save it. Let's just use the default name, i.e. Project1.
  • In the main form, drop a TLabel and give its Caption property value of "Enter Numbers Only". Leave its default name of Label1.
  • In the main form, drop a TEdit just below Label1. Align them so they have the same Left position. Let this TEdit to have default name of Edit1.
  • In the main form, drop a TStatusBar. Leave it to have default name of StatusBar1. Double click on it to open its Panels property editor. Add a TStatusPanel there.

After doing the above steps you would get a form like shown below.

Form1_Design001.png


Coding

  • In the interface section of main form's unit, declare a custom exception class with name of EUnacceptableChars. Declare it like this:
      EUnacceptableChars=class(Exception);
    

    It's fine to declare it just before the main form's declaration.
  • Add a private class variable in the main form with name of FOldEditWndProc with type of TWndMethod.
  • To the main form, add a private procedure named CustomWndProc which takes only one var parameter with type of TMessage. Something like this:
      TForm1 = class(TForm)
        ...
        ...
      private
        FOldEditWndProc: TWndMethod;
        procedure CustomWndProc(var AMsg: TMessage);
        ...
        ...
      end;
    

    And use the following for CustomWndProc implementation.

    procedure TForm1.CustomWndProc(var AMsg: TMessage);
    var
      S: string;
      i: Integer;
    begin
      // handle pasting message only
      if AMsg.Msg=WM_PASTE then
      begin
        // Get text version of clipboard content
        S := Clipboard.AsText;
        // check if the content of the clipboard convertible to integer, which means
        // the content is number(s) only
        if (S <> '') and not TryStrToInt(S, i) then
          // Raise EUnacceptableChars exception
          raise EUnacceptableChars.Create('Unacceptable character(s)');
      end;
    
      // Call the original message handler
      FOldEditWndProc(AMsg);
    end;
    
  • Back to design view, double click on client area of the main form to generate skeleton code of its OnCreate event handler. Use the following codes for the implementation.

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      // set the ES_NUMBER "number only" style
      SetWindowLong(Edit1.Handle
                    , GWL_STYLE
                    , GetWindowLong(Edit1.Handle, GWL_STYLE) or ES_NUMBER
                   );
    
      // mention the windows version information             
      if IsVistaOrAbove then
        StatusBar1.Panels[0].Text := 'Vista or Above'
      else
        StatusBar1.Panels[0].Text := 'Windows Prior Vista';
    
      {$IFDEF Unicode}
      // Unicode Delphi programs only need to subclass WndProc (for custom handling
      // pasting operation) when running in in Windows prior to Vista
      if not IsVistaOrAbove then
      begin
        FOldEditWndProc := Edit1.WindowProc;
        Edit1.WindowProc := CustomWndProc;
      end;
      {$ELSE}
      // Non-unicode Delphi programs always, so far, need the subclasing
      FOldEditWndProc := Edit1.WindowProc;
      Edit1.WindowProc := CustomWndProc;
      {$ENDIF}
    end;
    
  • Now add IsVistaOrAbove local function just before the TForm1.FormCreate method implementation. The codes like shown below.

    function IsVistaOrAbove: Boolean;
    var
      vOSVer: TOSVersionInfo;
    begin
      vOSVer.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
      GetVersionEx(vOSVer);
      Result := vOSVer.dwMajorVersion > 5;
    end;
    

I believe that's it for coding. Let's run the non-unicode version of our demo project. Press F9 to run it. Initially you would get something like the following.

Form1_Run001.png

You can try to type in a few numbers to the TEdit. it would be just fine, just like shown below.

Form1_Run001_TypeNumbersOnly.png

But you would notice that the edit control would not accept non-number characters. Now, what about pasting? Let's copy "CodeCall" to clipboard. It certainly containing non-number characters. What would happen if we paste it to that edit control? What would happened is shown below.

PastingNonNumbers_001.png

Yes, an exception with message of "Unacceptable character(s)" will be raised. In fact that exception class was EUnacceptableChars.

Here is the complete source code of the demo project: Attached File  Project1.zip   208.02KB   134 downloads. Feel free to use or improve it.

Now what about unicode?

If you have unicode version of Delphi, like Delphi 2010, XE, XE2, and XE3, just compile the demo Project1 with it, and run it. In this test case we use Delphi XE2, under Windows 7.

When first run we get something like shown below. Note that yours might be different due to different graphical settings (theme, aero selection, etc).

Form1_Run002_UnicodeDelphiIn7.png

There should be no problem when you are typing number, like shown below.

Form1_Run003_UnicodeDelphiIn7_TypingNumbers.png

But if you try to type non-numbers character or pasting text containing non-number character(s), you will get something like the following.

Form1_Run004_UnicodeDelphiIn7_TypingNonNumbers.png

Note that the "exception" was raised by Windows, not by Delphi code.
  • 0





Also tagged with one or more of these keywords: tedit, number-only, es_number, windows api, gui format