IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Composants VCL Delphi Discussion :

Changer la propriété Text d'un TEdit dérivé provoque un crash !


Sujet :

Composants VCL Delphi

  1. #1
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut Changer la propriété Text d'un TEdit dérivé provoque un crash !
    Bonjour,

    Je suis sous W11 avec Delphi 6 Personal Edition.

    Je veux créer un TEdit dérivé pour la saisie de valeurs numériques en format flottant.

    Je fais ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    type TFloatEdit = class(TEdit)
      private
        fSigned: boolean;
        fKommaPresent: boolean;
        fSignPresent: boolean;
        fIntegerPart: integer;
        fFractionalPart: integer;
      published
        constructor CreateNew(aSig: boolean; aInt, aFract: integer);
        destructor Destroy; override;
        procedure SetText(aValue: string);
        procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
    end;
     
    // =============== TFloatEdit =================
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      inherited;
      fsigned := aSig;
      if aInt<1 then aInt := 1;
      fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aFract;
      OnKeyPress := FloatEditKeyPress;
    end;
     
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;
      inherited;
    end;
     
    procedure TFloatEdit.SetText(aValue: string);
    begin
      Text := aValue;                                         // <<<<<<<<<<< ici crash violation de mémoire !!!!!!!!!!
      fKommaPresent := (pos('.',aValue)>0) or (pos(',',aValue)>0);
      fSignPresent := (pos('+',aValue)>0) or (pos('-',aValue)>0);;
    end;
     
     
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p: integer;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin
        Key := #0;
        exit;
      end;
      s := Text;
      if (Key=',') or (Key='.') then begin
        if fFractionalPart=0 then begin
          Key := #0;
        end else begin
          if fKommaPresent then begin
            p := pos('.',s);
            s := StringReplace(s,'.','',[rfReplaceAll]);
            Key := #0;
            fKommaPresent := false;
          end else begin
            fKommaPresent := true;
          end;
        end;
        exit;
      end;
      if (Key=',') or (Key='.') then begin
        if fSignPresent then s := MidStr(s,2,200);
        s := Key + s;
        Text := s;
        Key := #0;
      end;
    end;
    La création de passe bien (pas de crash au moins).
    Mais si je veux initialiser le contenu Text de mon TFloatEdit, il y a une violation de mémoire - voir le commentaire dans le source ci-dessus.

    A l'évidence, je me suis planté. Mais où ? Je n'arrive pas à comprendre...
    Merci de votre aide !

  2. #2
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    6 026
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 6 026
    Par défaut
    Il y a la fonction Val() pour contrôler la validité d'un chaîne "numérique" (y compris l'éventuel signe).

    Ensuite surcharge de DefaultHandler pour la validation des autres possibilités d'insertion.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    type
      TFloatEdit = class(TEdit)
      private
        function GetNewText(const aInsertText :string) :string;
        function Validate(const aText :string): boolean;
      public
        procedure DefaultHandler(var Message); override;
      end;
     
    uses ClipBrd;
     
    function TFloatEdit.GetNewText(const aInsertText: string): string;
    begin
      Result := Text;
      Delete(Result, SelStart +1, SelLength);
      Insert(aInsertText, Result, SelStart +1);
    end;
     
    function TFloatEdit.Validate(const aText: string): boolean;
    var
      Value: double;
      Code: integer;
    begin
      // Val ne fonctionne qu'avec le point
      Val(StringReplace(aText, FormatSettings.DecimalSeparator, '.', []), Value, Code);
      Result := Code = 0;
    end;
     
    procedure TFloatEdit.DefaultHandler(var Message);
    var
      NewText: string;
    begin
      NewText := '';
     
      case TMessage(Message).Msg of
        EM_REPLACESEL : NewText := GetNewText(string(TMessage(Message).LParam));
        WM_CHAR       : if TWMChar(Message).CharCode <> ord(VK_BACK) then
                          NewText := GetNewText(char(TWMChar(Message).CharCode));
        WM_PASTE      : if Clipboard.HasFormat(CF_TEXT) then
                          NewText := GetNewText(Clipboard.AsText);
        WM_SETTEXT    : NewText := TWMSetText(Message).Text;
      end;
     
      if (Length(NewText) = 0) or Validate(NewText) then
        inherited;
    end;

  3. #3
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Merci pour ta réponse. J'ai intégré comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    type TFloatEdit = class(TEdit)
      private
        fSigned: boolean;
        fKommaPresent: boolean;
        fSignPresent: boolean;
        fIntegerPart: integer;
        fFractionalPart: integer;
      published
        constructor CreateNew(aSig: boolean; aInt, aFract: integer);
        destructor Destroy; override;
        procedure SetValue(const aValue: string);
        procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
        procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
    end;
     
    // =============== TFloatEdit =================
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      inherited;
      fsigned := aSig;
      if aInt<1 then aInt := 1;
      fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aFract;
      OnKeyPress := FloatEditKeyPress;
    end;
     
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;
      inherited;
    end;
     
    procedure TFloatEdit.SetValue(const aValue: string);
    begin
      { s'assurer que le contrôle est valide avant d'écrire dans Text }
      if csDestroying in ComponentState then Exit;
      Text := aValue;                               // <<<<<<<<<<<<<<<<<<< toujours le crash ici !!!!!
      fKommaPresent := (Pos('.', aValue) > 0) or (Pos(',', aValue) > 0);
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);
    end;
     
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p: integer;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin
        Key := #0;
        exit;
      end;
      s := Text;
      if (Key=',') or (Key='.') then begin
        if fFractionalPart=0 then begin
          Key := #0;
        end else begin
          if fKommaPresent then begin
            p := pos('.',s);
            s := StringReplace(s,'.','',[rfReplaceAll]);
            Key := #0;
            fKommaPresent := false;
          end else begin
            fKommaPresent := true;
          end;
        end;
        exit;
      end;
      if (Key=',') or (Key='.') then begin
        if fSignPresent then s := MidStr(s,2,200);
        s := Key + s;
        Text := s;
        Key := #0;
      end;
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);
    begin
      inherited;
      { reconstruire les flags après édition (utile après collage ou suppressions) }
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
    Et je crée mon objet de la manière suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    var
      MyEdit: TObject;
    ...
     
                     MyEdit:= TObject(TFloatEdit.CreateNew(True,9,2));
                     TFloatEdit(MyEdit).SetValue('0.00');      //  <<<<<<<<<<<<<<< cet appel à SetValue provoque le crash !
    Donc, il est clair qu'à cet instant, aucune action ne peut être en cours sur mon objet.
    Voici une capture du message d'erreur:
    Nom : Capture d'écran 2025-10-05 175138.png
Affichages : 294
Taille : 12,8 Ko

    Le crash se produit lors de la lecture de l'adresse 0...0, même pas au niveau d'une écriture en mémoire.

  4. #4
    Membre expérimenté
    Avatar de XeGregory
    Homme Profil pro
    Passionné par la programmation
    Inscrit en
    Janvier 2017
    Messages
    743
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Passionné par la programmation
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Janvier 2017
    Messages : 743
    Billets dans le blog
    1
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    type
      TFloatEdit = class(TEdit)
      private
        fSigned: Boolean;
        fKommaPresent: Boolean;
        fSignPresent: Boolean;
        fIntegerPart: Integer;
        fFractionalPart: Integer;
      public
        constructor CreateNew(AOwner: TComponent; aSig: Boolean; aInt, aFract: Integer);
        destructor Destroy; override;
        procedure SetValue(const aValue: string);
        procedure FloatEditKeyPress(Sender: TObject; var Key: Char);
        procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
      end;
     
    constructor TFloatEdit.CreateNew(AOwner: TComponent; aSig: Boolean; aInt, aFract: Integer);
    begin
      inherited Create(AOwner);                     // <-- important
      fsigned := aSig;
      if aInt < 1 then aInt := 1;
      fIntegerPart := aInt;
      if aFract < 0 then aFract := 0;
      fFractionalPart := aFract;
      OnKeyPress := FloatEditKeyPress;
    end;
     
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;
      inherited;
    end;
     
    procedure TFloatEdit.SetValue(const aValue: string);
    begin
      if csDestroying in ComponentState then Exit;
      // protéger l'accès à Text si le Handle n'est pas encore créé
      if not HandleAllocated then
      begin
        // stocker temporairement dans TextBuffer ou assigner après création
        Text := aValue; // Create(AOwner) a déjà appelé inherited; normalement OK
      end
      else
        Text := aValue;
      fKommaPresent := (Pos('.', aValue) > 0) or (Pos(',', aValue) > 0);
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);
    end;
     
    procedure TFloatEdit.FloatEditKeyPress(Sender: TObject; var Key: Char);
    var
      s: string;
      p: Integer;
    begin
      if not (Key in ['0'..'9', '.', ',', '+', '-', #8]) then
      begin
        Key := #0;
        Exit;
      end;
      s := Text;
      if (Key = ',') or (Key = '.') then
      begin
        if fFractionalPart = 0 then
        begin
          Key := #0;
        end
        else
        begin
          if fKommaPresent then
          begin
            p := Pos('.', s);
            s := StringReplace(s, '.', '', [rfReplaceAll]);
            Key := #0;
            fKommaPresent := False;
          end
          else
            fKommaPresent := True;
        end;
        Exit;
      end;
      if (Key = ',') or (Key = '.') then
      begin
        if fSignPresent then
          s := MidStr(s, 2, 200);
        s := Key + s;
        Text := s;
        Key := #0;
      end;
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);
    begin
      inherited;
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    var
      MyEdit: TFloatEdit;
    begin
      MyEdit := TFloatEdit.CreateNew(Self, True, 9, 2); 
      MyEdit.Parent := Self; 
      MyEdit.SetValue('0.00');
    end;

  5. #5
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    6 026
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 6 026
    Par défaut
    Que tu veuilles nommer un constructeur CreateNew pourquoi pas mais TEdit n'a pas cette méthode. inherited ne sert dès lors à rien et ton contrôle n'est pas entièrement initialisé puisque le constructeur Create n'est pas appelé.

    Il faudrait au moins :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      // inherited;
      Create(nil);

  6. #6
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Merci, Andnotor !
    Avec ton conseil, le crash est éliminé.
    Maintenant, je peux aller plus loin...

  7. #7
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Je remercie XeGregory et AndNotor pour leurs aides décisives.

    Ceci m'a permis de finaliser une première version de mon objet TFloatEdit.
    Ce n'est pas encore à l'épreuve de toutes les fausses manipulations, mais ça viendra.
    Au moins, cela montre l'esprit de ce que je voulais faire.

    En voici le source:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    unit KGF_unit_FloatEdit;
     
    {
      KGF_unit_FloadEdit
        derived from TEdit, designed to enter floating point values
        the input format is [+|-]Fn.m
          with:    n=integer part (including the optional sign)
                   m = fractional part (excluding the decimal separator)
        The current version forces the decimal separator to ".".
     
      Special thanks to XeGregory and AndNotor. Their help has been decisif !
      This piece of software if totally free of charge for anybody and any useage.
      Their is no warranty nor garanty whatsoever.
     
      Final notice:
      The TFloatEdit component advantageously replaces TMaskEdit for the scope of its action.
      At any position, entering a sign character (- or +) changes the sign without changing the cursor position.
      At any position, a decimal separator removes an existing separator without changing the cursor position.
      When no decimal separator is present, entering a decimal separator inserts it at the current position.
     
      Author:  Klaus Fischer
      Created: 06/10/2025
      Language: Delphi 6 Personal Edition
      Version: V1.0
     
      Modification history:
      Date         Version     Author           Notes
      -------------------------------------------------------------------------------------
      06/10/2025   V1.0        Klaus            Initial version
     
    }
     
     
    interface
      uses
        Controls, StdCtrls, Classes, SysUtils, StrUtils;
     
    type TFloatEdit = class(TEdit)
      private
        fSigned: boolean;                   // flag "a +/- sign may be present"
        fKommaPresent: boolean;             // internal flag: "a decimal point is present"
        fSignPresent: boolean;              // internal flag: "a sign is present"
        fIntegerPart: integer;              // maximum length of the integer part (including the optiional sign)
        fFractionalPart: integer;           // maximum length of the fractional part (excluding the decimal point)
        fActualIntegerPart: integer;        // internal variable: actual length of the integer part
        fActualFractionalPart: integer;     // internal variable: actual length of the fractional part
      published
        constructor CreateNew(aSig: boolean; aInt, aFract: integer);
        destructor Destroy; override;
     
        procedure SetValue(const aValue: string);    // DO NOT USE DIRECTLY THE TEXT PROPERTY !
        procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
        procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
     
        // configuration properties
        property Signed: boolean read fSigned write fSigned;
        property IntegerPart: integer read fIntegerPart write fIntegerPart;
        property FractionalPart: integer read fFractionalPart write fFractionalPart;
     
    end;
     
    implementation
     
    // =============== TFloatEdit =================
     
    // creates the object initially defining some values
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      Create(nil);                                                                  // contribution from AndNotor
      fsigned := aSig;
      if aInt<1 then aInt := 1;
      fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aFract;
      fActualIntegerPart := 0;
      fActualFractionalPart := 0;
      OnKeyPress := FloatEditKeyPress;
    end;
     
    // properly remove the object
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;
      inherited;
    end;
     
    // method to replace the TEXT property which MUST NOT be used !
    procedure TFloatEdit.SetValue(const aValue: string);
    var
      s: string;
      p, ni, nf: integer;
    begin
      { s'assurer que le contrôle est valide avant d'écrire dans Text }
      if csDestroying in ComponentState then Exit;                                  // contribution from XeGregory
      s := aValue;
      s := StringReplace(s,',','.',[rfReplaceAll]);                                 // force "." as decimal point
      p := Pos('.', aValue);
      fKommaPresent := Pos('.', aValue) > 0;                                        // remember if a decimal point is present
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);             // remember if a sign is present
      if fSignPresent and not Signed then exit;                                     // a ort if sign present and not allowed
      if fKommaPresent then begin                                                   // determine the size of both parts
        ni := p - 1;
        nf := Length(s) - p;
      end else begin                                                                // determine the size of sole integer part
       ni := Length(s);
       nf := 0;
      end;
      if (ni>fIntegerPart) or (nf>fFractionalPart) then exit;                       // abort if size limits are exceeded
      Text := s;                                                                    // set the new text into the FloatEdit
    end;
     
    // event called at each key pressed while FloatEdit has focus
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p, p1, ni, nf: integer;
      KP, SP: boolean;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin                      // filter the allowed character set
        Key := #0;
        exit;
      end;
      s := Text;                                                                    // get the actual content of the control
     
      // decimal separator key
      if (Key=',') or (Key='.') then begin                                          // a decimal separator was entered ?
        if fFractionalPart=0 then begin                                             // no fractional part allowed ?
          Key := #0;
        end else begin                                                              // here, fractional part is allowed
          if fKommaPresent then begin                                               // have we actually a decimal separator ?
            p := pos('.',s);                                                        // find it
            p1 := SelStart;                                                         // remember current cursor position
            if p1>p then p1 := p1 - 1;                                              // anticipate the deletion
            s := StringReplace(s,'.','',[rfReplaceAll]);                            // remove it
            SetValue(s);                                                            // set the new text value
            SelStart := p1;                                                         // restore the cursor position
            Key := #0;                                                              // Key is fully handled
            fKommaPresent := false;                                                 // remember "no decimal point present"
          end else begin                                                            // here, no decimal point was present
            fKommaPresent := true;                                                  // so, remember it now
          end;                                                                      // and let Key action take place
        end;
        exit;                                                                       // all done for decimal separator key
      end;
     
      // sign key
      if (Key='-') or (Key='+') then begin                                          // a sign key was entered ?
        p := SelStart;                                                              // save the cursor position
        if fSignPresent then p := p + 1;                                            // update the cursor position after insertion
        if fSignPresent then s := MidStr(s,2,200);                                  // if a sign was present, so remove it
        s := Key + s;                                                               // add the new sign
        SetValue(s);                                                                // update the text in the control
        SelStart := p;                                                              // restore the cursor position
        Key := #0;                                                                  // Key is fully handled
      end;                                                                          // all done for sign key
     
      // DEL key
      p1 := self.GetSelStart;                                                       // get the current cursor position
      if Key=#8 then if p1>0 then Delete(s,p1,1);                                   // if not at beginning, remove the preceeding character
     
      // here, handle all numeric characters
      // recheck the internal markers
      p := Pos('.', s);
      KP := p > 0;                                                                  // build temporary decimal point flag
      SP := (Pos('+', s) > 0) or (Pos('-', s) > 0);                                 // build temporary sign flag
      if KP then begin                                                              // determine length of both parts
        ni := p - 1;
        nf := Length(s) - p;
      end else begin                                                                // determine length of sole fractional part
       ni := Length(s);
       nf := 0;
      end;
      // now abort if position in integer part and max length achieved, same for fractional part
      if ((p1<=ni) and (ni>=fIntegerPart)) or ((p1>ni) and (nf>=fFractionalPart)) then begin
        Key := #0;                                                                  // abort if maximum length achieved
        exit;
      end;
      SelStart := p - 1;                                                            // restore the cursor position
      fKommaPresent := KP;                                                          // definitively update the internal markers
      fSignPresent := SP;
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);                     // contribution of XeGregory
    begin
      inherited;
      { reconstruire les flags après édition (utile après collage ou suppressions) }
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
     
     
    end.

  8. #8
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    6 026
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 6 026
    Par défaut
    Pense tout de même que CreateNew ne sera pas appelé si le composant est déposé depuis la palette. Tu devrais plutôt procéder ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    type TFloatEdit = class(TEdit)
    public
      constructor Create(aOwner: TComponent); override;
      constructor CreateNew(aSig: boolean; aInt, aFract: integer);
    end;
     
    constructor TFloatEdit.Create(aOwner: TComponent);
    begin
      inherited;
      fIntegerPart := 1;
      OnKeyPress := FloatEditKeyPress;
    end;
     
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      Create(nil);                                                           
      fsigned := aSig;
      if aInt>=1 then fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aFract;
    end;
    Il serait aussi plus logique que aInt et aFract soient des cardinal puisque tu ne veux que du positif.

    Et enfin mettre de l'ordre dans la déclaration :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    type TFloatEdit = class(TEdit)
    private
      fSigned: boolean;                   // flag "a +/- sign may be present"
      fKommaPresent: boolean;             // internal flag: "a decimal point is present"
      fSignPresent: boolean;              // internal flag: "a sign is present"
      fIntegerPart: integer;              // maximum length of the integer part (including the optiional sign)
      fFractionalPart: integer;           // maximum length of the fractional part (excluding the decimal point)
      fActualIntegerPart: integer;        // internal variable: actual length of the integer part
      fActualFractionalPart: integer;     // internal variable: actual length of the fractional part
      procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
      procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
    public
      constructor Create(aOwner: TComponent); override;
      constructor CreateNew(aSig: boolean; aInt, aFract: integer);
      destructor Destroy; override;
      procedure SetValue(const aValue: string);    // DO NOT USE DIRECTLY THE TEXT PROPERTY !
    published
      property Signed: boolean read fSigned write fSigned;
      property IntegerPart: integer read fIntegerPart write fIntegerPart;
      property FractionalPart: integer read fFractionalPart write fFractionalPart;
    end;

  9. #9
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Merci, AndNotor !
    J'ai appliqué tes suggestions.

    En même temps, j'ai corrigé de petites anomalies.
    J'ai aussi remplacé l'utilisation de SelStart en lecture par la méthode GetCurrentCharacterPos.
    Ceci est nécessaire car l'utilisation des flèches ne change pas SelStart, mais est bien prise en compte par la position du Caret.
    J'ai aussi ajouté une propriété Alignment pour pouvoir aligner l'affichage à droite.

    Voici le source opértionnel:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    unit KGF_unit_FloatEdit;
     
    {
      KGF_unit_FloadEdit
        derived from TEdit, designed to enter floating point values
        the input format is [+|-]Fn.m
          with:    n=integer part (including the optional sign)
                   m = fractional part (excluding the decimal separator)
        The current version forces the decimal separator to ".".
     
      Special thanks to XeGregory and AndNotor. Their help has been decisif !
      This piece of software if totally free of charge for anybody and any useage.
      Their is no warranty nor garanty whatsoever.
     
      Final notice:
        The TFloatEdit component advantageously replaces TMaskEdit for the scope of its action.
        At any position, entering a sign character (- or +) changes the sign without changing the cursor position.
        At any position, a decimal separator removes an existing separator without changing the cursor position.
        When no decimal separator is present, entering a decimal separator inserts it at the current position.
        In any case, invalid characters are ignored, valid characters in invalid positions are ignored.
     
      Author:  Klaus Fischer
      Created: 06/10/2025
      Language: Delphi 6 Personal Edition
      Version: V1.1
      Contact: fischer.klaus@orange.fr
     
      Modification history:
      Date         Version     Author           Notes
      -------------------------------------------------------------------------------------
      06/10/2025   V1.0        Klaus            Initial version
      07/10/2025   V1.1        Klaus            applying tip from AndNotor: separating methods and dubbling the constructor
                                                replacing "get SelStart" by GetCurrentCharacterPos method
                                                adding the Alignment property
                                                  (found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control)
    }
     
     
    interface
      uses
        Windows, Messages, Controls, StdCtrls, Classes, SysUtils, StrUtils, Types, Dialogs;
     
    type TGetCaretPos = function(var aPoint: TPoint): boolean; stdcall;
     
    type TFloatEdit = class(TEdit)
    private
      fSigned: boolean;                   // flag "a +/- sign may be present"
      fAlignment:TAlignment;
      fKommaPresent: boolean;             // internal flag: "a decimal point is present"
      fSignPresent: boolean;              // internal flag: "a sign is present"
      fIntegerPart: integer;              // maximum length of the integer part (including the optiional sign)
      fFractionalPart: integer;           // maximum length of the fractional part (excluding the decimal point)
      fActualIntegerPart: integer;        // internal variable: actual length of the integer part
      fActualFractionalPart: integer;     // internal variable: actual length of the fractional part
      fWin32Handle: hwnd;                 // handle for dynamically loaded User32.dll
      fGetCaretPos: TGetCaretPos;         // loaded GetCaretPos function
      procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
      procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
      procedure SetAlignment(Value:TAlignment);
    protected
      procedure CreateParams(var Params:TCreateParams);override;                    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    public                                // contribution by AndNotor: separating public and published methods
      constructor Create(aOwner: TComponent); override;
      constructor CreateNew(aSig: boolean; aInt, aFract: integer);
      destructor Destroy; override;
      procedure SetValue(const aValue: string);    // DO NOT USE DIRECTLY THE TEXT PROPERTY !
      function GetCurrentCharacterPos: integer;
      procedure SetCurrentCharacterPos(p: integer);
    published
      property Signed: boolean read fSigned write fSigned;
      property IntegerPart: integer read fIntegerPart write fIntegerPart;
      property FractionalPart: integer read fFractionalPart write fFractionalPart;
      property Alignment:TAlignment read FAlignment write SetAlignment default taLeftJustify;
    end;
     
    implementation
     
    // =============== TFloatEdit =================
     
    // helper functions
     
    function TFloatEdit.GetCurrentCharacterPos: integer;
    var
      pt: TPoint;
    begin
      fGetCaretPos(pt);                                                             // get the real caret position in client coordinates
      result := SendMessage(Handle,EM_CHARFROMPOS,0,MAKELPARAM(pt.X,pt.Y));         // obtain the character position
    end;
     
    procedure TFloatEdit.SetCurrentCharacterPos(p: integer);
    var
      pt: TPoint;
    begin
      Selstart := p;                                                                // move the caret
    end;
     
    // creates the object initially defining some values
    constructor TFloatEdit.Create(aOwner: TComponent);      // contribution de AndNotor
    begin
      inherited;
      fsigned := true;
      fIntegerPart := 1;
      fFractionalPart := 2;
      fActualIntegerPart := 0;
      fActualFractionalPart := 0;
      fAlignment := taLeftJustify;
      OnKeyPress := FloatEditKeyPress;
      fWin32Handle := LoadLibrary('Win32.dll');
      fGetCaretPos := GetProcAddress(fWin32Handle,'GetCaretPos');
    end;
     
    // creates the object initially defining some values
    constructor TFloatEdit.CreateNew(aSig: boolean; aInt, aFract: integer);
    begin
      Create(nil);
      fsigned := aSig;
      if aInt<1 then aInt := 1;
      fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aFract;
      fActualIntegerPart := 0;
      fActualFractionalPart := 0;
      fAlignment := taLeftJustify;
      OnKeyPress := FloatEditKeyPress;
      fWin32Handle := LoadLibrary('User32.dll');
      fGetCaretPos := GetProcAddress(fWin32Handle,'GetCaretPos');
    end;
     
    // properly remove the object
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;                                                            // inactivate the KeyPress event
      FreeLibrary(fWin32Handle);                                                    // unload User32.dll
      inherited;
    end;
     
    // method to replace the TEXT property which MUST NOT be used !
    procedure TFloatEdit.SetValue(const aValue: string);
    var
      s: string;
      p, ni, nf: integer;
    begin
      { s'assurer que le contrôle est valide avant d'écrire dans Text }
      if csDestroying in ComponentState then Exit;                                  // contribution from XeGregory
      s := aValue;                                                                  // get the new value
      s := StringReplace(s,',','.',[rfReplaceAll]);                                 // force "." as decimal point
      p := Pos('.', aValue);                                                        // search for a decimal point
      fKommaPresent := Pos('.', aValue) > 0;                                        // remember if a decimal point is present
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);             // remember if a sign is present
      if fSignPresent and not Signed then exit;                                     // abort if sign present and not allowed
      if fKommaPresent then begin                                                   // check if sign present
        ni := p - 1;                                                                // yes: determine the size of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine the size of sole integer part
       nf := 0;
      end;
      if (ni>fIntegerPart) or (nf>fFractionalPart) then exit;                       // abort if size limits are exceeded
      Text := s;                                                                    // set the new text into the FloatEdit
    end;
     
    // event called at each key pressed while FloatEdit has focus
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p, p1, ni, nf, cp: integer;
      KP, SP: boolean;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin                      // filter the allowed character set
        Key := #0;
        exit;
      end;
      s := Text;                                                                    // get the actual content of the control
      cp := GetCurrentCharacterPos + 1;                                             // get the actual character position
     
      // decimal separator key
      if (Key=',') or (Key='.') then begin                                          // a decimal separator was entered ?
        if fFractionalPart=0 then begin                                             // no fractional part allowed ?
          Key := #0;
        end else begin                                                              // here, fractional part is allowed
          if fKommaPresent then begin                                               // have we actually a decimal separator ?
            p := pos('.',s);                                                        // find it
            if cp>=p then cp := cp - 1;                                             // anticipate the deletion
            s := StringReplace(s,'.','',[rfReplaceAll]);                            // remove it
            Text := s;                                                              // set the new text value
            SetCurrentCharacterPos(cp);                                             // restore the cursor position
            Key := #0;                                                              // Key is fully handled
            fKommaPresent := false;                                                 // remember "no decimal point present"
          end else begin                                                            // here, no decimal point was present
            p := cp;                                                                // get the actual cursor position
            if p<(Length(s)-fFractionalPart) then begin                             // to many potential fractional digits ?
              Key := #0;                                                            // so abort this action !
              exit;
            end;
            fKommaPresent := true;                                                  // so, remember it now
          end;                                                                      // and let Key action take place
        end;
        exit;                                                                       // all done for decimal separator key
      end;
     
      // sign key
      if (Key='-') or (Key='+') then begin                                          // a sign key was entered ?
        if fSignPresent then Delete(s,1,1)                                          // if a sign was present, so remove it
                        else cp := cp + 1;                                          // update the cursor position after insertion
        s := Key + s;                                                               // add the new sign
        Text := s;                                                                  // update the text in the control
        fSignPresent := true;                                                       // remeber the sign presence
        SetCurrentCharacterPos(cp-1);                                               // restore the cursor position
        Key := #0;                                                                  // Key is fully handled
        exit;                                                                       // all done for sign key
      end;
     
      // DEL key
      if Key=#8 then begin                                                          // DEL key ?
        if cp>0 then begin                                                          // if not at beginning:
          cp := cp - 1;
          Delete(s,cp,1);                                                           // remove the preceeding character
          SetValue(s);                                                              // update the text AND all iinternal flags
          SetCurrentCharacterPos(cp-1);                                             // update the cursor position
        end;
        Key := #0;                                                                  // all done for DEL key
        exit;
      end;
     
      // here, handle all numeric characters
      // recheck the internal markers
      p := Pos('.', s);                                                             // check if decimal sepaator present
      KP := p > 0;                                                                  // build temporary decimal point flag
      SP := (Pos('+', s) > 0) or (Pos('-', s) > 0);                                 // build temporary sign flag
      if KP then begin                                                              // is decimal separator present ?
        ni := p - 1;                                                                // yes: determine length of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine length of sole fractional part
       nf := 0;
      end;
      // now abort if position in integer part and max length achieved, same for fractional part
      if ((cp<=ni) and (ni>=fIntegerPart)) or ((cp>ni) and (nf>=fFractionalPart)) then begin
        Key := #0;                                                                  // abort if maximum length achieved
        exit;
      end;
      // nos add the new character
      s := LeftStr(s,cp-1) + Key + MidStr(s,cp,Length(s));                          // build the new string
      SetValue(s);                                                                  // replace the value updating internal markers
      Key := #0;                                                                    // character handling colpleted
      SetCurrentCharacterPos(cp);                                                   // restore the cursor position
                                                                                    // here: all done for normal character
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);                     // contribution of XeGregory
    begin
      inherited;
      { reconstruire les flags après édition (utile après collage ou suppressions) }
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
     
    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    procedure TFloatEdit.SetAlignment(Value:TAlignment);
    begin
         if FAlignment<>Value
         then begin
                   FAlignment:=Value;
                   RecreateWnd;
              end;
    end;
     
    procedure TFloatEdit.CreateParams(var Params:TCreateParams);
    const
         Alignments:array[TAlignment] of Cardinal=(ES_LEFT,ES_RIGHT,ES_CENTER);
    begin
         inherited CreateParams(Params);
         Params.Style:=Params.Style or Alignments[FAlignment];
    end;      
     
    end.
    Maintenant, je peux saisir des valeurs monétaires en indiquant 2 décimales, par exemple:
    Nom : Capture d'écran 2025-10-07 101402.png
Affichages : 235
Taille : 403 octets

  10. #10
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    6 026
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 6 026
    Par défaut
    Ce qui est fait dans Create ne doit pas être refait dans CreateNew dû au inherited. Create contient les initialisations par défaut alors que l'utilisation de CreateNew n'est qu'optionnel.

    Passer par CreateNew entraîne une erreur sur la bibliothèque qui ne sera effectivement déchargée qu'à la terminaison du processus et non sur FreeLibrary puisque tu la charges deux fois sur le même handle. Pense que LoadLibrary incrémente un compteur que FreeLibrary décrémente, la libération n'est effective que lorsqu'il est à zéro.

  11. #11
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Ah, d'accord ! Je comprends mieux le "Create(nil)" dans CreateNew !

    Donc, je supprime le CreateNew et je fais mes paramétrages dans une nouvelle méthode "Configure":
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    unit KGF_unit_FloatEdit;
     
    {
      KGF_unit_FloadEdit
        derived from TEdit, designed to enter floating point values
        the input format is [+|-]Fn.m
          with:    n=integer part (including the optional sign)
                   m = fractional part (excluding the decimal separator)
        The current version forces the decimal separator to ".".
     
      Special thanks to XeGregory and AndNotor. Their help has been decisif !
      This piece of software if totally free of charge for anybody and any useage.
      Their is no warranty nor garanty whatsoever.
     
      Final notice:
        The TFloatEdit component advantageously replaces TMaskEdit for the scope of its action.
        At any position, entering a sign character (- or +) changes the sign without changing the cursor position.
        At any position, a decimal separator removes an existing separator without changing the cursor position.
        When no decimal separator is present, entering a decimal separator inserts it at the current position.
        In any case, invalid characters are ignored, valid characters in invalid positions are ignored.
     
      Author:  Klaus Fischer
      Created: 06/10/2025
      Language: Delphi 6 Personal Edition
      Version: V1.1
      Contact: fischer.klaus@orange.fr
     
      Modification history:
      Date         Version     Author           Notes
      -------------------------------------------------------------------------------------
      06/10/2025   V1.0        Klaus            Initial version
      07/10/2025   V1.1        Klaus            applying tip from AndNotor: separating methods and dubbling the constructor
                                                replacing "get SelStart" by GetCurrentCharacterPos method
                                                adding the Alignment property
                                                  (found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control)
    }
     
     
    interface
      uses
        Windows, Messages, Controls, StdCtrls, Classes, SysUtils, StrUtils, Types, Dialogs;
     
    type TGetCaretPos = function(var aPoint: TPoint): boolean; stdcall;
     
    type TFloatEdit = class(TEdit)
    private
      fSigned: boolean;                   // flag "a +/- sign may be present"
      fAlignment:TAlignment;
      fKommaPresent: boolean;             // internal flag: "a decimal point is present"
      fSignPresent: boolean;              // internal flag: "a sign is present"
      fIntegerPart: integer;              // maximum length of the integer part (including the optiional sign)
      fFractionalPart: integer;           // maximum length of the fractional part (excluding the decimal point)
      fActualIntegerPart: integer;        // internal variable: actual length of the integer part
      fActualFractionalPart: integer;     // internal variable: actual length of the fractional part
      fWin32Handle: hwnd;                 // handle for dynamically loaded User32.dll
      fGetCaretPos: TGetCaretPos;         // loaded GetCaretPos function
      procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
      procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
      procedure SetAlignment(Value:TAlignment);
    protected
      procedure CreateParams(var Params:TCreateParams);override;                    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    public                                // contribution by AndNotor: separating public and published methods
      constructor Create(aOwner: TComponent); override;
      destructor Destroy; override;
      procedure Configure(aSign: boolean; aInt, aFract: integer);
      procedure SetValue(const aValue: string);    // DO NOT USE DIRECTLY THE TEXT PROPERTY !
      function GetCurrentCharacterPos: integer;
      procedure SetCurrentCharacterPos(p: integer);
    published
      property Signed: boolean read fSigned write fSigned;
      property IntegerPart: integer read fIntegerPart write fIntegerPart;
      property FractionalPart: integer read fFractionalPart write fFractionalPart;
      property Alignment:TAlignment read FAlignment write SetAlignment default taLeftJustify;
    end;
     
    implementation
     
    // =============== TFloatEdit =================
     
    // helper functions
     
    function TFloatEdit.GetCurrentCharacterPos: integer;
    var
      pt: TPoint;
    begin
      fGetCaretPos(pt);                                                             // get the real caret position in client coordinates
      result := SendMessage(Handle,EM_CHARFROMPOS,0,MAKELPARAM(pt.X,pt.Y));         // obtain the character position
    end;
     
    procedure TFloatEdit.SetCurrentCharacterPos(p: integer);
    var
      pt: TPoint;
    begin
      Selstart := p;                                                                // move the caret
    end;
     
    // creates the object initially defining some values
    constructor TFloatEdit.Create(aOwner: TComponent);      // contribution de AndNotor
    begin
      inherited;
      fsigned := true;
      fFractionalPart := 2;
      fActualIntegerPart := 0;
      fActualFractionalPart := 0;
      fAlignment := taLeftJustify;
      OnKeyPress := FloatEditKeyPress;
      fWin32Handle := LoadLibrary('User32.dll');
      fGetCaretPos := GetProcAddress(fWin32Handle,'GetCaretPos');
    end;
     
    // change some configuration options
    procedure TFloatEdit.Configure(aSign: boolean; aInt, aFract: integer);
    begin
      fSigned := aSign;
      if aInt<0 then aInt := 1;
      fIntegerPart := aInt;
      if aFract<0 then aFract := 0;
      fFractionalPart := aInt;
    end;
     
    // properly remove the object
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;                                                            // inactivate the KeyPress event
      FreeLibrary(fWin32Handle);                                                    // unload User32.dll
      inherited;
    end;
     
    // method to replace the TEXT property which MUST NOT be used !
    procedure TFloatEdit.SetValue(const aValue: string);
    var
      s: string;
      p, ni, nf: integer;
    begin
      { s'assurer que le contrôle est valide avant d'écrire dans Text }
      if csDestroying in ComponentState then Exit;                                  // contribution from XeGregory
      s := aValue;                                                                  // get the new value
      s := StringReplace(s,',','.',[rfReplaceAll]);                                 // force "." as decimal point
      p := Pos('.', aValue);                                                        // search for a decimal point
      fKommaPresent := Pos('.', aValue) > 0;                                        // remember if a decimal point is present
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);             // remember if a sign is present
      if fSignPresent and not Signed then exit;                                     // abort if sign present and not allowed
      if fKommaPresent then begin                                                   // check if sign present
        ni := p - 1;                                                                // yes: determine the size of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine the size of sole integer part
       nf := 0;
      end;
      if (ni>fIntegerPart) or (nf>fFractionalPart) then exit;                       // abort if size limits are exceeded
      Text := s;                                                                    // set the new text into the FloatEdit
    end;
     
    // event called at each key pressed while FloatEdit has focus
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p, p1, ni, nf, cp: integer;
      KP, SP: boolean;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin                      // filter the allowed character set
        Key := #0;
        exit;
      end;
      s := Text;                                                                    // get the actual content of the control
      cp := GetCurrentCharacterPos + 1;                                             // get the actual character position
     
      // decimal separator key
      if (Key=',') or (Key='.') then begin                                          // a decimal separator was entered ?
        if fFractionalPart=0 then begin                                             // no fractional part allowed ?
          Key := #0;
        end else begin                                                              // here, fractional part is allowed
          if fKommaPresent then begin                                               // have we actually a decimal separator ?
            p := pos('.',s);                                                        // find it
            if cp>=p then cp := cp - 1;                                             // anticipate the deletion
            s := StringReplace(s,'.','',[rfReplaceAll]);                            // remove it
            Text := s;                                                              // set the new text value
            SetCurrentCharacterPos(cp);                                             // restore the cursor position
            Key := #0;                                                              // Key is fully handled
            fKommaPresent := false;                                                 // remember "no decimal point present"
          end else begin                                                            // here, no decimal point was present
            p := cp;                                                                // get the actual cursor position
            if p<(Length(s)-fFractionalPart) then begin                             // to many potential fractional digits ?
              Key := #0;                                                            // so abort this action !
              exit;
            end;
            fKommaPresent := true;                                                  // so, remember it now
          end;                                                                      // and let Key action take place
        end;
        exit;                                                                       // all done for decimal separator key
      end;
     
      // sign key
      if (Key='-') or (Key='+') then begin                                          // a sign key was entered ?
        if fSignPresent then Delete(s,1,1)                                          // if a sign was present, so remove it
                        else cp := cp + 1;                                          // update the cursor position after insertion
        s := Key + s;                                                               // add the new sign
        Text := s;                                                                  // update the text in the control
        fSignPresent := true;                                                       // remeber the sign presence
        SetCurrentCharacterPos(cp-1);                                               // restore the cursor position
        Key := #0;                                                                  // Key is fully handled
        exit;                                                                       // all done for sign key
      end;
     
      // DEL key
      if Key=#8 then begin                                                          // DEL key ?
        if cp>0 then begin                                                          // if not at beginning:
          cp := cp - 1;
          Delete(s,cp,1);                                                           // remove the preceeding character
          SetValue(s);                                                              // update the text AND all iinternal flags
          SetCurrentCharacterPos(cp-1);                                             // update the cursor position
        end;
        Key := #0;                                                                  // all done for DEL key
        exit;
      end;
     
      // here, handle all numeric characters
      // recheck the internal markers
      p := Pos('.', s);                                                             // check if decimal sepaator present
      KP := p > 0;                                                                  // build temporary decimal point flag
      SP := (Pos('+', s) > 0) or (Pos('-', s) > 0);                                 // build temporary sign flag
      if KP then begin                                                              // is decimal separator present ?
        ni := p - 1;                                                                // yes: determine length of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine length of sole fractional part
       nf := 0;
      end;
      // now abort if position in integer part and max length achieved, same for fractional part
      if ((cp<=ni) and (ni>=fIntegerPart)) or ((cp>ni) and (nf>=fFractionalPart)) then begin
        Key := #0;                                                                  // abort if maximum length achieved
        exit;
      end;
      // nos add the new character
      s := LeftStr(s,cp-1) + Key + MidStr(s,cp,Length(s));                          // build the new string
      SetValue(s);                                                                  // replace the value updating internal markers
      Key := #0;                                                                    // character handling colpleted
      SetCurrentCharacterPos(cp);                                                   // restore the cursor position
                                                                                    // here: all done for normal character
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);                     // contribution of XeGregory
    begin
      inherited;
      { reconstruire les flags après édition (utile après collage ou suppressions) }
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
     
    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    procedure TFloatEdit.SetAlignment(Value:TAlignment);
    begin
         if FAlignment<>Value
         then begin
                   FAlignment:=Value;
                   RecreateWnd;
              end;
    end;
     
    procedure TFloatEdit.CreateParams(var Params:TCreateParams);
    const
         Alignments:array[TAlignment] of Cardinal=(ES_LEFT,ES_RIGHT,ES_CENTER);
    begin
         inherited CreateParams(Params);
         Params.Style:=Params.Style or Alignments[FAlignment];
    end;      
     
    end.

  12. #12
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    6 026
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 6 026
    Par défaut
    Bon reprenons !

    Une méthode de configuration n'a pas grand intérêt. Même si tu le crées dynamiquement, tu l'initialiserais ainsi (inséré depuis la palette, les propriétés sont définies à travers l'inspecteur d'objet) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    FloatEdit1 := TFloatEdit.Create(Self);
    FloatEdit1.Signed := FALSE;
    FloatEdit1.IntegerPart := 3;
    // etc.
    Mais comme implémenté actuellement, rien n'interdit FloatEdit1.IntegerPart := -3 puisque les propriétés ne sont pas protégées contre des valeurs invalides ; et une méthode Configure n'y changera rien.

    Ces tests doivent être fait dans les accesseurs en écriture de chaque propriété, IntegerPart par exemple devrait être codé ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      type TFloatEdit = class(TEdit)
      private
        fIntegerPart: integer;
        procedure SetIntegerPart(const Value: integer);
      published
        property IntegerPart: integer read fIntegerPart write SetIntegerPart;
      end;
     
    procedure TFloatEdit.SetIntegerPart(const Value: integer);
    begin
      if fIntegerPart <> Value then
        if Value > 0 then
        begin
          fIntegerPart := Value;
          // Autre traitement si nécessaire (tronquer l'affichage ?)
        end;
    end;
    C'est la base de l'encapsulation en POO.

  13. #13
    Expert éminent
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    14 277
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 14 277
    Par défaut
    Si c'est le projet habituel, cet Edit sera utilisé depuis PANORAMIC, donc il y a aura surement une fonction de la DLL genre ConfigureFloatEdit, c'est plutôt elle qui devra contenir le code de TFloatEdit.Configure.

    Suis le conseil de Andnotor, laisse TFloatEdit plutôt conforme POO / Standard VCL pour une utilisation générique
    Et spécialise son utilisation uniquement dans la DLL PANORAMIC
    Aide via F1 - Utilisez l'I.A. - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité ! Sachez-le : l'IA remplace la très grande majorité des développeurs, pas seulement les ignares ...

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  14. #14
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    Ok, reprenons:
    La méthode Configure a été supprimé.
    Dans les propriétés IntegerPart et FractionalPart, l'écriture se fait par une procédure Set... .

    Voilà le code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    unit KGF_unit_FloatEdit;
     
    {
      KGF_unit_FloadEdit
        derived from TEdit, designed to enter floating point values
        the input format is [+|-]Fn.m
          with:    n=integer part (including the optional sign)
                   m = fractional part (excluding the decimal separator)
        The current version forces the decimal separator to ".".
     
      Special thanks to XeGregory and AndNotor. Their help has been decisif !
      This piece of software if totally free of charge for anybody and any useage.
      Their is no warranty nor garanty whatsoever.
     
      Final notice:
        The TFloatEdit component advantageously replaces TMaskEdit for the scope of its action.
        At any position, entering a sign character (- or +) changes the sign without changing the cursor position.
        At any position, a decimal separator removes an existing separator without changing the cursor position.
        When no decimal separator is present, entering a decimal separator inserts it at the current position.
        In any case, invalid characters are ignored, valid characters in invalid positions are ignored.
     
      Author:  Klaus Fischer
      Created: 06/10/2025
      Language: Delphi 6 Personal Edition
      Version: V1.1
      Contact: fischer.klaus@orange.fr
     
      Modification history:
      Date         Version     Author           Notes
      -------------------------------------------------------------------------------------
      06/10/2025   V1.0        Klaus            Initial version
      07/10/2025   V1.1        Klaus            applying tip from AndNotor: separating methods and dubbling the constructor
                                                replacing "get SelStart" by GetCurrentCaretPos method
                                                adding the Alignment property
                                                  (found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control)
                                                Remove CreateNew, generalize Create and add some properties
     
    }
     
     
    interface
      uses
        Windows, Messages, Controls, StdCtrls, Classes, SysUtils, StrUtils, Types, Dialogs;
     
    type TGetCaretPos = function(var aPoint: TPoint): boolean; stdcall;      // function dynamically loaded from User32.dll
     
    type TFloatEdit = class(TEdit)
    private
      fSigned: boolean;                   // flag "a +/- sign may be present"
      fAlignment:TAlignment;              // alignment Left or Right (default: Left)
      fKommaPresent: boolean;             // internal flag: "a decimal point is present"
      fSignPresent: boolean;              // internal flag: "a sign is present"
      fIntegerPart: integer;              // maximum length of the integer part (including the optiional sign)
      fFractionalPart: integer;           // maximum length of the fractional part (excluding the decimal point)
      fActualIntegerPart: integer;        // internal variable: actual length of the integer part
      fActualFractionalPart: integer;     // internal variable: actual length of the fractional part
      fWin32Handle: hwnd;                 // handle for dynamically loaded User32.dll
      fGetCaretPos: TGetCaretPos;         // loaded GetCaretPos function
      procedure FloatEditKeyPress(Sender : TObject; var Key : Char);
      procedure FloatEditCMExit(var Message: TCMExit); message CM_EXIT;
      procedure SetAlignment(Value:TAlignment);
      procedure SetIntegerPart(aValue: integer);                                    // contribution by AndNotor
      procedure SetFractionalPart(aValue:  integer);                                // contribution by AndNotor
    protected
      procedure CreateParams(var Params:TCreateParams);override;                    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    public                                // contribution by AndNotor: separating public and published methods
      constructor Create(aOwner: TComponent); override;
      destructor Destroy; override;
      procedure SetValue(const aValue: string);    // DO NOT USE DIRECTLY THE TEXT PROPERTY !
      function GetCurrentCharacterPos: integer;                                     // use function from User32.dll
      procedure SetCurrentCharacterPos(p: integer);                                 // use Selstart
    published
      // configuration properties
      property Signed: boolean read fSigned write fSigned;
      property IntegerPart: integer read fIntegerPart write SetIntegerPart;
      property FractionalPart: integer read fFractionalPart write SetFractionalPart;
      property Alignment:TAlignment read FAlignment write SetAlignment default taLeftJustify;
    end;
     
    implementation
     
    // =============== TFloatEdit =================
     
    // helper functions
     
    function TFloatEdit.GetCurrentCharacterPos: integer;
    var
      pt: TPoint;
    begin
      fGetCaretPos(pt);                                                             // get the real caret position in client coordinates
      result := SendMessage(Handle,EM_CHARFROMPOS,0,MAKELPARAM(pt.X,pt.Y));         // obtain the character position
    end;
     
    procedure TFloatEdit.SetCurrentCharacterPos(p: integer);
    var
      pt: TPoint;
    begin
      Selstart := p;                                                                // move the caret
    end;
     
    // creates the object initially defining some values
    constructor TFloatEdit.Create(aOwner: TComponent);      // contribution de AndNotor
    begin
      inherited;
      fsigned := true;
      fFractionalPart := 2;
      fActualIntegerPart := 0;
      fActualFractionalPart := 0;
      fAlignment := taLeftJustify;
      OnKeyPress := FloatEditKeyPress;
      fWin32Handle := LoadLibrary('User32.dll');
      fGetCaretPos := GetProcAddress(fWin32Handle,'GetCaretPos');
    end;
     
    // properly remove the object
    destructor TFloatEdit.Destroy;
    begin
      OnKeyPress := nil;                                                            // inactivate the KeyPress event
      FreeLibrary(fWin32Handle);                                                    // unload User32.dll
      inherited;
    end;
     
    // secure parameter modification
    procedure TFloatEdit.SetIntegerPart(aValue: integer);
    begin
      if aValue<0 then aValue := 1;
      fIntegerPart := aValue;
    end;
     
    procedure TFloatEdit.SetFractionalPart(aValue: integer);
    begin
      if aValue<0 then aValue := 0;
      fFractionalPart := aValue;
    end;
     
    // method to replace the TEXT property which MUST NOT be used !
    procedure TFloatEdit.SetValue(const aValue: string);
    var
      s: string;
      p, ni, nf: integer;
    begin
      { s'assurer que le contrôle est valide avant d'écrire dans Text }
      if csDestroying in ComponentState then Exit;                                  // contribution from XeGregory
      s := aValue;                                                                  // get the new value
      s := StringReplace(s,',','.',[rfReplaceAll]);                                 // force "." as decimal point
      p := Pos('.', aValue);                                                        // search for a decimal point
      fKommaPresent := Pos('.', aValue) > 0;                                        // remember if a decimal point is present
      fSignPresent := (Pos('+', aValue) > 0) or (Pos('-', aValue) > 0);             // remember if a sign is present
      if fSignPresent and not Signed then exit;                                     // abort if sign present and not allowed
      if fKommaPresent then begin                                                   // check if sign present
        ni := p - 1;                                                                // yes: determine the size of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine the size of sole integer part
       nf := 0;
      end;
      if (ni>fIntegerPart) or (nf>fFractionalPart) then exit;                       // abort if size limits are exceeded
      Text := s;                                                                    // set the new text into the FloatEdit
    end;
     
    // event called at each key pressed while FloatEdit has focus
    procedure TFloatEdit.FloatEditKeyPress(Sender : TObject; var Key : Char);
    var
      s: string;
      p, p1, ni, nf, cp: integer;
      KP, SP: boolean;
    begin
      if not (Key in ['0'..'9','.',',','+','-',#8]) then begin                      // filter the allowed character set
        Key := #0;
        exit;
      end;
      s := Text;                                                                    // get the actual content of the control
      cp := GetCurrentCharacterPos + 1;                                             // get the actual character position
     
      // decimal separator key
      if (Key=',') or (Key='.') then begin                                          // a decimal separator was entered ?
        if fFractionalPart=0 then begin                                             // no fractional part allowed ?
          Key := #0;                                                                // so ignore the key
        end else begin                                                              // here, fractional part is allowed
          if fKommaPresent then begin                                               // have we actually a decimal separator ?
            p := pos('.',s);                                                        // find it
            if cp>=p then cp := cp - 1;                                             // anticipate the deletion
            s := StringReplace(s,'.','',[rfReplaceAll]);                            // remove it
            Text := s;                                                              // set the new text value
            SetCurrentCharacterPos(cp);                                             // restore the cursor position
            Key := #0;                                                              // Key is fully handled
            fKommaPresent := false;                                                 // remember "no decimal point present"
          end else begin                                                            // here, no decimal point was present
            p := cp;                                                                // get the actual cursor position
            if p<(Length(s)-fFractionalPart) then begin                             // to many potential fractional digits ?
              Key := #0;                                                            // so abort this action !
              exit;
            end;
            fKommaPresent := true;                                                  // so, remember it now
          end;                                                                      // and let Key action take place
        end;
        exit;                                                                       // all done for decimal separator key
      end;
     
      // sign key
      if (Key='-') or (Key='+') then begin                                          // a sign key was entered ?
        if fSignPresent then Delete(s,1,1)                                          // if a sign was present, so remove it
                        else cp := cp + 1;                                          // update the cursor position after insertion
        s := Key + s;                                                               // add the new sign
        Text := s;                                                                  // update the text in the control
        fSignPresent := true;                                                       // remeber the sign presence
        SetCurrentCharacterPos(cp-1);                                               // restore the cursor position
        Key := #0;                                                                  // Key is fully handled
        exit;                                                                       // all done for sign key
      end;
     
      // DEL key
      if Key=#8 then begin                                                          // DEL key ?
        if cp>0 then begin                                                          // if not at beginning:
          cp := cp - 1;
          Delete(s,cp,1);                                                           // remove the preceeding character
          SetValue(s);                                                              // update the text AND all iinternal flags
          SetCurrentCharacterPos(cp-1);                                             // update the cursor position
        end;
        Key := #0;                                                                  // all done for DEL key
        exit;
      end;
     
      // here, handle all numeric characters
      // recheck the internal markers
      p := Pos('.', s);                                                             // check if decimal sepaator present
      KP := p > 0;                                                                  // build temporary decimal point flag
      SP := (Pos('+', s) > 0) or (Pos('-', s) > 0);                                 // build temporary sign flag
      if KP then begin                                                              // is decimal separator present ?
        ni := p - 1;                                                                // yes: determine length of both parts
        nf := Length(s) - p;
      end else begin
       ni := Length(s);                                                             // no: determine length of sole fractional part
       nf := 0;
      end;
      // now abort if position in integer part and max length achieved, same for fractional part
      if ((cp<=ni) and (ni>=fIntegerPart)) or ((cp>ni) and (nf>=fFractionalPart)) then begin
        Key := #0;                                                                  // abort if maximum length achieved
        exit;
      end;
      // now add the new character
      s := LeftStr(s,cp-1) + Key + MidStr(s,cp,Length(s));                          // build the new string
      SetValue(s);                                                                  // replace the value updating internal markers
      Key := #0;                                                                    // character handling colpleted
      SetCurrentCharacterPos(cp);                                                   // restore the cursor position
                                                                                    // here: all done for normal character
    end;
     
    procedure TFloatEdit.FloatEditCMExit(var Message: TCMExit);                     // contribution of XeGregory
    begin
      inherited;
      { reconstruire les flags après édition (utile après collage ou suppressions) }
      fKommaPresent := (Pos('.', Text) > 0) or (Pos(',', Text) > 0);
      fSignPresent := (Pos('+', Text) > 0) or (Pos('-', Text) > 0);
    end;
     
    // found at https://stackoverflow.com/questions/4455355/how-to-set-textalignment-in-tedit-control
    procedure TFloatEdit.SetAlignment(Value:TAlignment);
    begin
         if fAlignment<>Value
         then begin
                   fAlignment := Value;                                             // change the alignment
                   RecreateWnd;                                                     // and redisplay the visual part
              end;
    end;
     
    procedure TFloatEdit.CreateParams(var Params:TCreateParams);
    const
         Alignments:array[TAlignment] of Cardinal=(ES_LEFT,ES_RIGHT,ES_CENTER);
    begin
         inherited CreateParams(Params);
         Params.Style:=Params.Style or Alignments[FAlignment];
    end;
     
    end.
    @ShaiLeTroll:
    Oui, je l'utiliserai à partir de Panoramic, mais pas seulement.
    Pour mes besoins personnels, j'écris souvent directement en Delphi, et c'est surtout là que j'en ai besoin actuellement.
    Bien sûr, accessoirement, je le rendrai accessible à Panoramic via ma DLL utilitaire.

    Je me suis battu longuement avec MaskEdit, mais je n'ai pas trouvé le moyen de définir un masque permettant de saisir un flottant de façon "intuitive".
    L'affichage du masque, le déplacement dans le masque et le changement des données saisies conduit souvent à des anomalies.
    Donc, je voulais faire un composant permettant de saisir, selon les paramètres:
    - un entier non signé
    - un entier signé
    - un flottant non signé
    - un flottant signé
    le tout en spécifiant le nombre de chiffre avant la virgule et, le cas échéant, le nombre de chiffre après la virgule.

    Je pense que j'ai à peu près atteint mon but, fonctionnellement. Il est vrai qu'il aurait fallu mieux sécuriser la définition des propriétés. Dont acte.
    Il peut bien sûr encore y avoir des bugs, évidemment. Mais l'idée de base était de réaliser un composant d'utilisation générale dans le cadre indiqué ci-dessus.

  15. #15
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    427
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Informaticien retraité
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2010
    Messages : 427
    Billets dans le blog
    1
    Par défaut
    J'ai étendu les fonctionnalités de mon composant FloatEdit, en ajoutant un contrôle optionnel d'une fourchette de valeurs.
    En pièce jointe il y a un fichier ZIP avec le projet complet.

    Voici une capture d'écran du programme de test:
    Nom : Capture d'écran 2025-10-23 020837.png
Affichages : 196
Taille : 10,8 Ko
    Fichiers attachés Fichiers attachés

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. changer dynamiquement le texte d'un bouton
    Par cgodefrw dans le forum Access
    Réponses: 2
    Dernier message: 14/04/2006, 10h35
  2. Changer couleur de texte de ligne
    Par uloaccess dans le forum Access
    Réponses: 2
    Dernier message: 26/01/2006, 17h10
  3. Changer dynamiquement le texte d'un bouton
    Par memess dans le forum Flash
    Réponses: 2
    Dernier message: 04/11/2005, 09h38
  4. Changer l'alignement du texte d'un TEdit
    Par programaniac dans le forum Composants VCL
    Réponses: 4
    Dernier message: 29/10/2005, 17h46
  5. Comment changer la taille Text < 8
    Par Xavier dans le forum C++Builder
    Réponses: 4
    Dernier message: 14/10/2004, 08h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo