Jump to content




Recent Status Updates

View All Updates

Binpress - Cut your development time and costs in half
Photo
- - - - -

Numbers Only TEdit Descendant

tedit numbers only es_number class descendant

  • Please log in to reply
No replies to this topic

#1 Luthfi

Luthfi

    CC Leader

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

Posted 14 October 2012 - 03:41 AM

In previous tutorial we have learned how to make an instance of TEdit to only accept number characters. That solution was working fine in all versions of Delphi 32 bit and any kind of Windows 32bit. However the biggest disadvantage of the approach used in that tutorial is that the implementation was done in form level. It was called form level because the codes is written in the hosting form or in the hosting form's unit.

This approach means that usually you will have to repeat some, maybe most, of the codes if you need to add more TEdit. For example, using that tutorial's approach means that if you want to have 4 numbers only TEdit in a form, then in the form's OnCreate event you have to do SetWindowLong 4 times. You also need 4 TWndMethod class variable to store the message handlers of 4 TEdit. Not only that, you also need to manage the WM_PASTE overriding.

What about if you need to have several forms that each need to host several numbers only TEdit? Now you see that your job might grows exponentially.

The best answer would be to implement the "numbers" only limitation of descendant class of TEdit. With this solution, all you have to do is to use instances of this new descendant, and zero code in form level. In fact, there is a trick that allows you to automatically replace all TEdit in a form with your own custom TEdit just by adding a unit to your uses list in correct order.

In this tutorial we will use TEditEx for the name of this extended TEdit. We want TEditEx to only accept only numbers when it told to, by setting a property. Let's name this property NumbersOnly and make its type of boolean. This way we hope that it will compatible with TEdit in XE2 and after. Note that for compatibility, i.e. to avoid duplicate property clash if projects using TEditEx compiled in Delphi XE2 or above, we will use compiler directives to remove this property and related codes if the class is used in Delphi XE2.

Put the following block of compiler directives in the beginning of the unit. Just before or after interface keyword is okay.

{$IFDEF VER230}
  {$DEFINE XE2_OR_ABOVE}
{$ELSE}
  {$IFDEF VER240}
    {$DEFINE XE2_OR_ABOVE}
  {$ENDIF}
{$ENDIF}

Basically the above compiler directives block checks the version of compiler that compile the code. XE2 has VER230 for identifier, and XE3 got VER240. Once either of these was detected, the block does {$DEFINE XE2_OR_ABOVE} which define symbol XE2_OR_ABOVE. This symbol can be queried later (by another compiler directives).

Sometimes we don't want users to be notified through messages if they entered unacceptable characters. We just want to ignore the problem. Therefore let's add a property that control the action TEditEx should take when unacceptable characters was detected. First we need to declare an enumeration type for this, let's name it TInvalidCharAction, then add property InvalidCharAction with the type of TInvalidCharAction to TEditEx.

On handling WM_PASTE, we don't have to resort to write a whole message handler like we had to in form level. We just need to have a method which takes a variable parameter with type either TWMPaste or TMessage, then mark it so it will be invoked whenever TEditEx receives WM_PASTE messages. Something like this (note the message keyword and WM_PASTE identifier that follow the method declaration).

type
  TEditEx=class(StdCtrls.TEdit)
  private
    ...
    procedure HandlePaste(var AMsg: TWMPaste); message WM_PASTE;
  ...
  end;

I think we have covered all important things. So start by declaring TEditEx in interface section of the unit like this:

type
  TInvalidCharAction = (icaIgnore, icaException);

  TEditEx=class(StdCtrls.TEdit)
  private
    {$IFNDEF XE2_OR_ABOVE}
    FNumbersOnly: Boolean;
    FInvalidCharAction: TInvalidCharAction;
    procedure SetIsNumbersOnly(const Value: Boolean);
    procedure HandlePaste(var AMsg: TWMPaste); message WM_PASTE;
    {$ENDIF}
  published
    {$IFNDEF XE2_OR_ABOVE}
    property NumbersOnly: Boolean read FNumbersOnly write SetIsNumbersOnly default False;
    property InvalidCharAction: TInvalidCharAction read FInvalidCharAction write FInvalidCharAction default icaIgnore;
    {$ENDIF}
  end;


procedure SetIsNumbersOnly(const Value: Boolean);

In this method we want to adjust the style of TEditEx to receive numbers only or not according to the given Value parameter content. So the implementation goes like below.

{$IFNDEF XE2_OR_ABOVE}
procedure TEditEx.SetIsNumbersOnly(const Value: Boolean);
begin
  if FNumbersOnly=Value then Exit;
  FNumbersOnly := Value;
  if FNumbersOnly then
    SetWindowLong(Handle
                  , GWL_STYLE
                  , GetWindowLong(Self.Handle, GWL_STYLE)
                    or ES_NUMBER
                 )
  else
    SetWindowLong(Handle
                  , GWL_STYLE
                  , GetWindowLong(Self.Handle, GWL_STYLE)
                    and not ES_NUMBER
                 )
end;
{$ENDIF}


procedure HandlePaste(var AMsg: TWMPaste); message WM_PASTE;

This method will automatically be invoked anytime TEditEx receives WM_PASTE message. Of course here we want to inspect the content of the clipboard and see if it's valid according to the TEditEx current state. So the implementation is:

{$IFNDEF XE2_OR_ABOVE}
procedure TEditEx.HandlePaste(var AMsg: TWMPaste);
var
  S: string;
  i: Integer;
begin
  {$IFDEF Unicode}
  // Unicode Delphi programs only need to do the custom handling
  // pasting operation when running in in Windows prior to Vista
  if IsVistaOrAbove then
  begin
    inherited;
    Exit;
  end;
  {$ENDIF}

  if not FNumbersOnly or IsVistaOrAbove then
    inherited
  else begin
    S := Clipboard.AsText;
    if (S<>'') and TryStrToInt(S, i) then
      inherited
    else if FInvalidCharAction=icaException then
      raise Exception.Create('Sorry, "' + S + '" contains unaccepted characters');
  end;
end;
{$ENDIF}

And now our TEditEx is completed. Now time to put together a demo project to see if it's working as intended.


Demo Project

Note that we are going to be use a trick to "overwrite" ordinary TEdit with our TEditEx so that we don't need to install TEditEx to component pallette. For this to work, you have to add the following line in the interface section of TEditEx unit just after declaration of TEditEx.

type
  TEdit=class(TEditEx);

It declares TEdit as simple alias to TEditEx.


GUI Preparation

  • Create new project. Save it with name of TestEditEx.
  • Set the main form's name to frmTestEditEx. Save it as TestEditEx_MainForm.pas.
  • Drop a TEdit in the main form. Leave its default name as is (Edit1).
  • Just below Edit1, drop a TCheckBox. Give "Numbers Only" for its Caption property.
  • Drop a TLabel below CheckBox1. Set its Caption property to "Invalid character action".
  • Below the label, drop a TComboBox. It will automatically named ComboBox1.
    Set its Style property to csDropDownList. Add "Ignore" and "Raise exception" (in that order) into its Items property. Set its ItemIndex property to 0, to make "Ignore" item the selected one.

The main form should looked like this.

TestEditEx_Design001.png


Coding

  • In order to do the trick I mentioned before, put the name of the unit where TEditEx resides after the StdCtrls unit in interface uses list. So do this, add EditEx (the TEditEx unit name, in my case) somewhere after StdCtrls. Something like this.
      uses
        .., StdCtrls, .., EditEx, ..;
    

    This way Delphi compiler will assume that every TEdit it encounters is coming from EditEx unit. Because the units in later order ranked higher than prior units. If you want to avoid this, you have to include the unit name. Just like we did when declaring TEdit, i.e.:
      TEditEx=class(StdCtrls.Tedit)
    
  • Double click on Checkbox1 to generate skeleton code of its onchange event handler, and use the following codes for the handler.

    procedure TfrmTestEditEx.CheckBox1Click(Sender: TObject);
    begin
      Edit1.NumbersOnly := CheckBox1.Checked;
    end;
    
  • Double click on ComboBox1 to generate skeleton code of its onchange event handler, and use the following codes for the handler.

    procedure TfrmTestEditEx.ComboBox1Change(Sender: TObject);
    begin
      Edit1.InvalidCharAction := TInvalidCharAction(ComboBox1.ItemIndex);
    end;
    

I believe that's it. We have completed the demo project. Let's run it.


Running the Demo Project

Time to press F9 to execute the demo project from Delphi IDE. Initially you will get the following.

TestEditEx_Run001.png

In normal mode, i.e. when not limitting itself to accepting numbers only characters, Edit1 works just like ordinary TEdit. Like below.

TestEditEx_Run002_InStandardMode.png

But once we switch to the numbers only mode, i.e. by putting checkmark to CheckBox1, then you won't be able to type or paste in non-number characters.

TestEditEx_Run003_InNumbersOnlyMode_Ignoring.png

Furthermore, if you select "Raise exception" in ComboBox1 and try to paste text containing non-numbers to Edit1, you would get exception, something like shown below.

TestEditEx_Run003_InNumbersOnlyMode_Exception.png


Conclusion

Here is the full source code of TEditEx and the demo project: Attached File  Test_EditEx.zip   212.02KB   113 downloads. Feel free to use and improve it.
  • 0





Also tagged with one or more of these keywords: tedit, numbers only, es_number, class descendant

Powered by binpress