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 :

Problème avec OnEnter et OnExit sur les TEdit et dérivés


Sujet :

Composants VCL Delphi

  1. #1
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    407
    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 : 407
    Billets dans le blog
    1
    Par défaut Problème avec OnEnter et OnExit sur les TEdit et dérivés
    Je suis sous W11 avec Delphi 6 Personal Edition.

    Je n'arrive pas à m'en sortir avec les évènements OnEnter et OnExit d'un TEdit ou d'un objet dérivé de TEdit.

    Je sais (ou crois savoir) que OnEnter est déclenché lorsque le TEdit "gagne" le focus.
    Je sais (ou crois savoir) que OnExit est déclenché lorsque le TEdit "perd" le focus.

    D'après ce que je vois, un TEdit créé dans l'IDE se comporte effectivement comme ça.

    Or, un TEdit (ou un TEdit dérivé) créé par programme ne génère aucun de ces deux évènements, quelque soit le contexte.
    Ces objets créés par programme génèrent bien les évènements OnClick et OnKeyPress, mais JAMAIS OnEnter ni OnExit.

    Je joins un projet de démo dans un fichier ZIP pour montrer le problème. Ce programme crée 4 objets:
    - un TEdit créé par l'EDI Delphi
    - un TEdit standard créé par programme dans le OnCreate de la form principale
    - un TMyEdit dérivé de TEdit créé par programme dans le OnCreate de la form principale
    - un TMemo créé par l'IDE Delphi (servant à recevoir une ligne de texte pour chaque évènement déclenché)

    Au lancement, le TEdit créé par l'IDE a le focus, et la ligne corrrespondante apparaît dans le mémo

    Ensuite, on frappe la touche TAB. Effet etendu: OnExit dans le premier TEdit, OnEnter dans le TEdit créé par programme, curseur dans ce dernier.
    Or, on a bien OnExit sur le premier TEdit, mais OnEnter dans le TMemo, et le curseur est dans ce dernier.

    Une nouvelle frappe de TAB revient vers le TEdit créé par l'IDE, avec les évènements correspondants.

    Les deux objets créés par programme ne sont jamais atteints par TAB.

    Maintenant, un clic dans un de ces objets créé par programme génère bien un OnClick sur cet objet, mais PAS de OnEnter !
    Pire: la touche TAB ne semble avoir aucun effet: ni évènement généré ni focus déplacé
    Et même le clic dans un autre objet quelconque ne génère pas de OnExit sur l'objet créé par programme où était le curseur...

    J'ai encore dû rater quelque chose d'important. Pourriez-vous me mettre sur la voie ? Merci d'avance !

    Voici une capture d'écran:
    Nom : Capture d'écran 2025-10-25 134510.png
Affichages : 312
Taille : 9,4 Ko
    Fichiers attachés Fichiers attachés

  2. #2
    Membre expérimenté
    Avatar de XeGregory
    Homme Profil pro
    Passionné par la programmation
    Inscrit en
    Janvier 2017
    Messages
    670
    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 - Matériel informatique

    Informations forums :
    Inscription : Janvier 2017
    Messages : 670
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par KlausGunther Voir le message
    Je suis sous W11 avec Delphi 6 Personal Edition.

    Je n'arrive pas à m'en sortir avec les évènements OnEnter et OnExit d'un TEdit ou d'un objet dérivé de TEdit.

    Je sais (ou crois savoir) que OnEnter est déclenché lorsque le TEdit "gagne" le focus.
    Je sais (ou crois savoir) que OnExit est déclenché lorsque le TEdit "perd" le focus.

    D'après ce que je vois, un TEdit créé dans l'IDE se comporte effectivement comme ça.

    Or, un TEdit (ou un TEdit dérivé) créé par programme ne génère aucun de ces deux évènements, quelque soit le contexte.
    Ces objets créés par programme génèrent bien les évènements OnClick et OnKeyPress, mais JAMAIS OnEnter ni OnExit.

    Je joins un projet de démo dans un fichier ZIP pour montrer le problème. Ce programme crée 4 objets:
    - un TEdit créé par l'EDI Delphi
    - un TEdit standard créé par programme dans le OnCreate de la form principale
    - un TMyEdit dérivé de TEdit créé par programme dans le OnCreate de la form principale
    - un TMemo créé par l'IDE Delphi (servant à recevoir une ligne de texte pour chaque évènement déclenché)

    Au lancement, le TEdit créé par l'IDE a le focus, et la ligne corrrespondante apparaît dans le mémo

    Ensuite, on frappe la touche TAB. Effet etendu: OnExit dans le premier TEdit, OnEnter dans le TEdit créé par programme, curseur dans ce dernier.
    Or, on a bien OnExit sur le premier TEdit, mais OnEnter dans le TMemo, et le curseur est dans ce dernier.

    Une nouvelle frappe de TAB revient vers le TEdit créé par l'IDE, avec les évènements correspondants.

    Les deux objets créés par programme ne sont jamais atteints par TAB.

    Maintenant, un clic dans un de ces objets créé par programme génère bien un OnClick sur cet objet, mais PAS de OnEnter !
    Pire: la touche TAB ne semble avoir aucun effet: ni évènement généré ni focus déplacé
    Et même le clic dans un autre objet quelconque ne génère pas de OnExit sur l'objet créé par programme où était le curseur...

    J'ai encore dû rater quelque chose d'important. Pourriez-vous me mettre sur la voie ? Merci d'avance !

    Voici une capture d'écran:
    Nom : Capture d'écran 2025-10-25 134510.png
Affichages : 312
Taille : 9,4 Ko
    Bonjour KlausGunther,

    Parent : le contrôle doit avoir sa propriété Parent affectée (Form1 ou un TPanel). Sans Parent, pas d’affichage ni de focus.
    TabStop : doit être True pour que TAB atteigne le contrôle.
    TabOrder : positionner si nécessaire dans la chaîne de tabulation.

    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
    procedure TForm1.FormCreate(Sender: TObject);
    var
      E: TEdit;
    begin
      E := TEdit.Create(Self);
      E.Parent := Self;             // obligatoire
      E.Left := 20; E.Top := 60;
      E.Width := 200;
      E.Visible := True;
      E.Enabled := True;
      E.TabStop := True;            // pour que TAB l'atteigne
      E.TabOrder := 1; 
      E.OnEnter := EditEnter;
      E.OnExit  := EditExit;
    end;
    Exemple :

    Nom : Video_2025_10_25-1_edit_1.gif
Affichages : 188
Taille : 366,8 Ko

    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
    unit Unit1;
     
    interface
     
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
      System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
     
    type
      TMyEdit = class(TEdit)
      protected
        procedure DoEnter; override;
        procedure DoExit; override;
      end;
     
      TForm1 = class(TForm)
        EditDesign: TEdit;
        MemoLog: TMemo;
        procedure FormCreate(Sender: TObject);
        procedure EditDesignEnter(Sender: TObject);
        procedure EditDesignExit(Sender: TObject);
      private
        { Déclarations privées }
        EditRuntime: TEdit;
        MyEditRuntime: TMyEdit;
        procedure EditGenericEnter(Sender: TObject);
        procedure EditGenericExit(Sender: TObject);
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
    procedure TMyEdit.DoEnter;
    begin
      inherited; // indispensable pour que OnEnter se déclenche correctement
    end;
     
    procedure TMyEdit.DoExit;
    begin
      inherited; // indispensable pour que OnExit se déclenche correctement
    end;
     
    procedure TForm1.EditDesignEnter(Sender: TObject);
    begin
      MemoLog.Lines.Add('Enter: ' + TEdit(Sender).Name);
    end;
     
    procedure TForm1.EditDesignExit(Sender: TObject);
    begin
      MemoLog.Lines.Add('Exit: ' + TEdit(Sender).Name);
    end;
     
    procedure TForm1.EditGenericEnter(Sender: TObject);
    begin
      MemoLog.Lines.Add('Enter: ' + TEdit(Sender).Name);
    end;
     
    procedure TForm1.EditGenericExit(Sender: TObject);
    begin
      MemoLog.Lines.Add('Exit: ' + TEdit(Sender).Name);
    end;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      EditRuntime := TEdit.Create(Self);
      EditRuntime.Parent := Self; // obligatoire pour affichage et focus
      EditRuntime.Left := 20;
      EditRuntime.Top := EditDesign.Top + EditDesign.Height + 8;
      EditRuntime.Width := 200;
      EditRuntime.Visible := True;
      EditRuntime.Enabled := True;
      EditRuntime.TabStop := True; // important pour TAB
      EditRuntime.TabOrder := 1;
      EditRuntime.OnEnter := EditGenericEnter;
      EditRuntime.OnExit := EditGenericExit;
      EditRuntime.Name := 'EditRuntime';
     
      // --- TMyEdit créé à l'exécution (classe dérivée) ---
      MyEditRuntime := TMyEdit.Create(Self);
      MyEditRuntime.Parent := Self;
      MyEditRuntime.Left := EditRuntime.Left;
      MyEditRuntime.Top := EditRuntime.Top + EditRuntime.Height + 8;
      MyEditRuntime.Width := 200;
      MyEditRuntime.Visible := True;
      MyEditRuntime.Enabled := True;
      MyEditRuntime.TabStop := True; // important pour TAB
      MyEditRuntime.TabOrder := 2;
      MyEditRuntime.OnEnter := EditGenericEnter;
      MyEditRuntime.OnExit := EditGenericExit;
      MyEditRuntime.Name := 'MyEditRuntime';
     
      // --- Assurez-vous que le EditDesign présent sur la form a TabStop = True et TabOrder approprié
      EditDesign.TabStop := True;
      EditDesign.TabOrder := 0;
      EditDesign.OnEnter := EditDesignEnter;
      EditDesign.OnExit := EditDesignExit;
     
      // Optionnel : donner initialement le focus à EditDesign
      ActiveControl := EditDesign;
    end;

  3. #3
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    407
    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 : 407
    Billets dans le blog
    1
    Par défaut
    Merci, XeGregory !

    La ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    E.Parent := Self;             // obligatoire
    était décisive !

    C'est parfait: cela règle le problème dans mon programme de démo.
    Mais j'ai une difficulté qui va au-delà.
    En réalité, mon code de création d'objet est placé dans une DLL écrite en Delphi 6 PE.
    Mes objets dérivés de TEdit sont créés dans une fonction de cette DLL.
    Mais je n'ai que le handle de objet cible dans lequel mes objets générés doivent apparaître (TForm, TPanel, ...).
    Je n'ai pas l'objet lui-même.

    Comment puis-je pallier à ce manque ?

  4. #4
    Membre expérimenté
    Avatar de XeGregory
    Homme Profil pro
    Passionné par la programmation
    Inscrit en
    Janvier 2017
    Messages
    670
    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 - Matériel informatique

    Informations forums :
    Inscription : Janvier 2017
    Messages : 670
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par KlausGunther Voir le message
    Merci, XeGregory !

    La ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    E.Parent := Self;             // obligatoire
    était décisive !

    C'est parfait: cela règle le problème dans mon programme de démo.
    Mais j'ai une difficulté qui va au-delà.
    En réalité, mon code de création d'objet est placé dans une DLL écrite en Delphi 6 PE.
    Mes objets dérivés de TEdit sont créés dans une fonction de cette DLL.
    Mais je n'ai que le handle de objet cible dans lequel mes objets générés doivent apparaître (TForm, TPanel, ...).
    Je n'ai pas l'objet lui-même.

    Comment puis-je pallier à ce manque ?
    Si votre DLL n’a que le handle Windows (HWND) du parent, vous pouvez toujours créer des contrôles VCL dans la DLL et les rattacher à ce parent en affectant la propriété ParentWindow du contrôle.

    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
    function CreateMyEdit(ParentHandle: HWND; X, Y, W, H: Integer): HWND; stdcall;
    var
      E: TMyEdit;
    begin
      Result := 0;
      E := TMyEdit.Create(nil);
      try
        E.ParentWindow := ParentHandle;   // Rattache le contrôle au HWND fourni
        E.SetBounds(X, Y, W, H);
        E.Visible := True;
        E.Enabled := True;
        E.TabStop := True;  
        Result := E.Handle;
      except
        E.Free;
        raise;
      end;
    end;
     
    exports
      CreateMyEdit;
    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
     
     private
        FEditHandle: HWND;
     
    ....
     
    procedure TForm1.ButtonCreateClick(Sender: TObject);
    begin
      if FEditHandle = 0 then
        FEditHandle := CreateMyEdit(Self.Handle, 16, 40, 200, 22);
    end;
     
    procedure TForm1.ButtonDestroyClick(Sender: TObject);
    begin
      if FEditHandle <> 0 then
      begin
        // DestroyMyEdit(FEditHandle);
        FEditHandle := 0;
      end;
    end;

  5. #5
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    407
    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 : 407
    Billets dans le blog
    1
    Par défaut
    Ben... oui - c'est exactement ce que je faisais dans mon projet démo que j'avais posté.
    Je créais le TEdit en changant la propriété ParentWindow de l'objet créé.
    Il apparaît alors correctement dans la fenêtre ciblée, mais n'est justement pas capable de générer les OnEnter et OnExit...

  6. #6
    Membre expérimenté
    Avatar de XeGregory
    Homme Profil pro
    Passionné par la programmation
    Inscrit en
    Janvier 2017
    Messages
    670
    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 - Matériel informatique

    Informations forums :
    Inscription : Janvier 2017
    Messages : 670
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par KlausGunther Voir le message
    Ben... oui - c'est exactement ce que je faisais dans mon projet démo que j'avais posté.
    Je créais le TEdit en changant la propriété ParentWindow de l'objet créé.
    Il apparaît alors correctement dans la fenêtre ciblée, mais n'est justement pas capable de générer les OnEnter et OnExit...
    Tes procédure "OnEnter et OnExit" sont ou dans ta DLL ?

    Library DLL :

    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
    library MyEdits;
     
    uses
      Windows, SysUtils, Classes, Controls, StdCtrls;
     
    const
      WM_MYEDIT_ENTER = WM_USER + 1;
      WM_MYEDIT_EXIT  = WM_USER + 2;
     
    type
      TMyEdit = class(TEdit)
      protected
        procedure DoEnter; override;
        procedure DoExit; override;
      end;
     
    procedure TMyEdit.DoEnter;
    begin
      inherited;
      if ParentWindow <> 0 then
        PostMessage(ParentWindow, WM_MYEDIT_ENTER, WPARAM(Self.Handle), 0);
    end;
     
    procedure TMyEdit.DoExit;
    begin
      inherited;
      if ParentWindow <> 0 then
        PostMessage(ParentWindow, WM_MYEDIT_EXIT, WPARAM(Self.Handle), 0);
    end;
     
    { Gestion d'une liste d'instances pour pouvoir libérer plus tard }
    var
      GList: TList;
     
    function CreateMyEdit(ParentHandle: HWND; X, Y, W, H: Integer): HWND; stdcall;
    var
      E: TMyEdit;
    begin
      Result := 0;
      if ParentHandle = 0 then Exit;
      E := TMyEdit.Create(nil);           // Owner = nil, DLL garde la référence
      E.ParentWindow := ParentHandle;     // attache au HWND fourni
      E.SetBounds(X, Y, W, H);
      E.Visible := True;
      E.Enabled := True;
      E.TabStop := True;
      GList.Add(E);
      Result := E.Handle;
    end;
     
    function DestroyMyEdit(ControlHandle: HWND): BOOL; stdcall;
    var
      i: Integer;
      E: TMyEdit;
    begin
      Result := False;
      for i := GList.Count - 1 downto 0 do
      begin
        E := TMyEdit(GList[i]);
        if E.Handle = ControlHandle then
        begin
          GList.Delete(i);
          E.Free;
          Result := True;
          Exit;
        end;
      end;
    end;
     
    exports
      CreateMyEdit name 'CreateMyEdit',
      DestroyMyEdit name 'DestroyMyEdit';
     
    begin
      GList := TList.Create;
    end.
    Vcl :

    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
    unit Unit1;
     
    interface
     
    uses
      Windows, Messages, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls;
     
    const
      WM_MYEDIT_ENTER = WM_USER + 1;
      WM_MYEDIT_EXIT  = WM_USER + 2;
     
    function CreateMyEdit(ParentHandle: HWND; X, Y, W, H: Integer): HWND; stdcall; external 'MyEdits.dll';
    function DestroyMyEdit(ControlHandle: HWND): BOOL; stdcall; external 'MyEdits.dll';
     
    type
      TForm1 = class(TForm)
        ButtonCreate: TButton;
        ButtonDestroy: TButton;
        procedure FormCreate(Sender: TObject);
        procedure ButtonCreateClick(Sender: TObject);
        procedure ButtonDestroyClick(Sender: TObject);
      private
        FEditHandle: HWND;
        procedure WndProc(var Msg: TMessage); override;
      public
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      FEditHandle := 0;
    end;
     
    procedure TForm1.ButtonCreateClick(Sender: TObject);
    begin
      if FEditHandle = 0 then
        FEditHandle := CreateMyEdit(Self.Handle, 16, 40, 200, 22);
    end;
     
    procedure TForm1.ButtonDestroyClick(Sender: TObject);
    begin
      if FEditHandle <> 0 then
      begin
        DestroyMyEdit(FEditHandle);
        FEditHandle := 0;
      end;
    end;
     
    procedure TForm1.WndProc(var Msg: TMessage);
    begin
      if Msg.Msg = WM_MYEDIT_ENTER then
      begin
        ShowMessage('DLL edit Enter, HWND=' + IntToStr(Msg.WParam));
        Exit;
      end
      else if Msg.Msg = WM_MYEDIT_EXIT then
      begin
        ShowMessage('DLL edit Exit, HWND=' + IntToStr(Msg.WParam));
        Exit;
      end;
      inherited;
    end;
     
    end.

  7. #7
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    407
    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 : 407
    Billets dans le blog
    1
    Par défaut
    Oui, mes procédures sont dans la DLL, là ou les objets sont créés.

    J'ai essayé de recréer ce code, mais je n'ai pas réussi. Je vais continuer à essayer...

  8. #8
    Membre expérimenté
    Avatar de XeGregory
    Homme Profil pro
    Passionné par la programmation
    Inscrit en
    Janvier 2017
    Messages
    670
    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 - Matériel informatique

    Informations forums :
    Inscription : Janvier 2017
    Messages : 670
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par KlausGunther Voir le message
    J'ai essayé de recréer ce code, mais je n'ai pas réussi. Je vais continuer à essayer...
    Pourquoi ça ne fonctionnait pas
    Les identifiants de message n’étaient pas partagés correctement entre la DLL et l’exe : utiliser WM_USER + n dans chaque module donne des valeurs différentes, donc l’exe ne voyait pas les messages envoyés par la DLL.

    Dans cette version, j’ai utilisé RegisterWindowMessage avec la même chaîne dans la DLL et l’exécutable pour garantir un identifiant commun et permettre la réception correcte des messages. Les notifications sont envoyées depuis un seul handler fiable (WM_SETFOCUS / WM_KILLFOCUS). J’ai aussi défini ParentWindow et appelé SetParent pour que le contrôle soit réellement enfant du formulaire et que PostMessage cible le bon HWND.

    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
    library MyEdits;
     
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
      Vcl.Controls, Vcl.StdCtrls;
     
    // Identifiants de message partagés entre la DLL et l'EXE
    // L'EXE doit appeler RegisterWindowMessage avec les mêmes chaînes
    var
      MsgMyEditEnter: UINT; // identifiant pour notification "enter"
      MsgMyEditExit: UINT;  // identifiant pour notification "exit"
     
    // Liste des instances créées pour pouvoir les libérer proprement
    var
      GList: TList;
     
    type
      // TMyEdit dérive de TEdit et poste un message au parent lorsque
      // le contrôle reçoit ou perd le focus au niveau Windows.
      TMyEdit = class(TEdit)
      protected
        // On utilise les messages Windows WM_SETFOCUS / WM_KILLFOCUS
        // pour éviter les doublons provenant de plusieurs handlers VCL.
        procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
        procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS;
      end;
     
    { TMyEdit.WMSetFocus }
    procedure TMyEdit.WMSetFocus(var Msg: TWMSetFocus);
    begin
      inherited;
      // Si un parent a été précisé, poster la notification "enter"
      if ParentWindow <> 0 then
        PostMessage(ParentWindow, MsgMyEditEnter, WPARAM(Self.Handle), 0);
    end;
     
    { TMyEdit.WMKillFocus }
    procedure TMyEdit.WMKillFocus(var Msg: TWMKillFocus);
    begin
      inherited;
      // Si un parent a été précisé, poster la notification "exit"
      if ParentWindow <> 0 then
        PostMessage(ParentWindow, MsgMyEditExit, WPARAM(Self.Handle), 0);
    end;
     
    { CreateMyEdit
      Crée une instance de TMyEdit attachée au HWND fourni.
      - ParentHandle : HWND de la fenêtre hôte (généralement Form.Handle)
      - X,Y,W,H : position et taille relatives à la fenêtre hôte
      Retourne le handle Windows du contrôle créé (E.Handle) ou 0 si erreur.
    }
    function CreateMyEdit(ParentHandle: HWND; X, Y, W, H: Integer): HWND; stdcall;
    var
      E: TMyEdit;
    begin
      Result := 0;
      if ParentHandle = 0 then Exit; // handle parent invalide
     
      E := TMyEdit.Create(nil); // Owner = nil (DLL garde la référence)
      try
        // Assurer la relation parent au niveau VCL et Windows
        E.ParentWindow := ParentHandle;    // attache VCL au HWND fourni
        SetParent(E.Handle, ParentHandle); // assure la relation native Win32
        E.SetBounds(X, Y, W, H);           // positionner le contrôle
        E.Visible := True;
        E.Enabled := True;
        E.TabStop := True;
     
        // Ajouter à la liste pour pouvoir libérer plus tard
        GList.Add(E);
     
        // Retourner le handle du contrôle
        Result := E.Handle;
      except
        E.Free;
        raise; // remonter l'exception vers l'appelant si besoin
      end;
    end;
     
    { DestroyMyEdit
      Recherche dans la liste l'instance correspondant au Handle fourni,
      la supprime de la liste et la libère.
      Retourne True si destruction réussie, False sinon.
    }
    function DestroyMyEdit(ControlHandle: HWND): BOOL; stdcall;
    var
      i: Integer;
      E: TMyEdit;
    begin
      Result := False;
      // Parcours inverse pour suppression sûre durant l'itération
      for i := GList.Count - 1 downto 0 do
      begin
        E := TMyEdit(GList[i]);
        if E.Handle = ControlHandle then
        begin
          GList.Delete(i);
          E.Free;
          Result := True;
          Exit;
        end;
      end;
    end;
     
    exports
      CreateMyEdit name 'CreateMyEdit',
      DestroyMyEdit name 'DestroyMyEdit';
     
    { FreeGList
      Libère toutes les instances restantes et la TList elle-même.
      Enregistrée via AddExitProc pour être exécutée à la fin de la DLL.
    }
    procedure FreeGList;
    var
      i: Integer;
    begin
      if Assigned(GList) then
      begin
        for i := GList.Count - 1 downto 0 do
          TObject(GList[i]).Free;
        FreeAndNil(GList);
      end;
    end;
     
    begin
      // Enregistrer les messages partagés (chaînes identiques dans l'EXE)
      MsgMyEditEnter := RegisterWindowMessage('MYEDIT_ENTER_9F3A2B');
      MsgMyEditExit  := RegisterWindowMessage('MYEDIT_EXIT_9F3A2B');
     
      // Initialiser la liste d'instances
      GList := TList.Create;
     
      // S'assurer que les ressources sont libérées à la fermeture de la DLL
      AddExitProc(FreeGList);
    end.
    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
    unit Unit1;
     
    interface
     
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
     
    { Déclarations des fonctions exportées par la DLL MyEdits.
      L'EXE appelle ces fonctions pour créer/détruire les TEdit gérés dans la DLL. }
    function CreateMyEdit(ParentHandle: HWND; X, Y, W, H: Integer): HWND; stdcall;
      external 'MyEdits.dll';
    function DestroyMyEdit(ControlHandle: HWND): BOOL; stdcall;
      external 'MyEdits.dll';
     
    type
      TForm1 = class(TForm)
        ButtonCreate: TButton; // bouton pour créer un nouvel Edit via la DLL
        ButtonDestroy: TButton; // bouton pour détruire le dernier Edit créé
        Log: TMemo; // zone de log pour afficher les événements reçus
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure ButtonCreateClick(Sender: TObject);
        procedure ButtonDestroyClick(Sender: TObject);
      private
        BaseY: Integer; // position Y de départ pour le premier Edit
        Spacing: Integer; // espacement vertical entre les edits
        FEditHandles: TList; // liste des HWND des edits créés (stockés en tant que Pointer)
        FMsgMyEditEnter: UINT; // identifiant de message "enter" enregistré via RegisterWindowMessage
        FMsgMyEditExit: UINT; // identifiant de message "exit" enregistré via RegisterWindowMessage
      public
        { On override WndProc : réception des messages personnalisés envoyés par la DLL.
          Nous comparons Msg.Msg à FMsgMyEditEnter / FMsgMyEditExit (valeurs dynamiques). }
        procedure WndProc(var Msg: TMessage); override;
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
    { FormCreate
      - initialise la position de base et l'espacement
      - crée la liste FEditHandles
      - enregistre les identifiants de message partagés via RegisterWindowMessage
        IMPORTANT : la DLL doit appeler RegisterWindowMessage avec les mêmes chaînes
    }
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      BaseY := 20;
      Spacing := 30;
      FEditHandles := TList.Create;
     
      // Enregistrer les messages partagés; les chaînes doivent être identiques
      FMsgMyEditEnter := RegisterWindowMessage('MYEDIT_ENTER_9F3A2B');
      FMsgMyEditExit := RegisterWindowMessage('MYEDIT_EXIT_9F3A2B');
     
      // Afficher dans le memo les valeurs enregistrées (utile pour debug)
      Log.Lines.Add(PChar(Format('EXE: MsgEnter=%d MsgExit=%d', [FMsgMyEditEnter,
        FMsgMyEditExit])));
    end;
     
    { FormDestroy
      - détruit proprement tous les edits créés via la DLL avant fermeture
      - libère la liste FEditHandles
    }
    procedure TForm1.FormDestroy(Sender: TObject);
    var
      i: Integer;
      H: HWND;
    begin
      if Assigned(FEditHandles) then
      begin
        // Parcours inverse pour suppression sûre
        for i := FEditHandles.Count - 1 downto 0 do
        begin
          H := HWND(NativeInt(FEditHandles[i]));
          DestroyMyEdit(H); // demande à la DLL de libérer l'instance correspondante
          FEditHandles.Delete(i);
        end;
        FreeAndNil(FEditHandles);
      end;
    end;
     
    { ButtonCreateClick
      - calcule la position Y en fonction du nombre d'edits déjà créés
      - appelle CreateMyEdit dans la DLL en passant Self.Handle comme parent
      - en cas de succès, stocke le HWND retourné dans FEditHandles
    }
    procedure TForm1.ButtonCreateClick(Sender: TObject);
    var
      NewHWND: HWND;
      Index: Integer;
      YPos: Integer;
    begin
      Index := FEditHandles.Count;
      YPos := BaseY + Index * Spacing;
     
      // CreateMyEdit retourne le HWND du contrôle créé ou 0 en cas d'erreur
      NewHWND := CreateMyEdit(Self.Handle, 16, YPos, 200, 22);
      if NewHWND <> 0 then
        FEditHandles.Add(Pointer(NativeInt(NewHWND)))
      else
        Log.Lines.Add('Échec création edit');
    end;
     
    { ButtonDestroyClick
      - détruit le dernier Edit créé (LIFO)
      - enlève son handle de la liste si la destruction côté DLL réussit
    }
    procedure TForm1.ButtonDestroyClick(Sender: TObject);
    var
      LastIndex: Integer;
      H: HWND;
    begin
      if (FEditHandles = nil) or (FEditHandles.Count = 0) then
        Exit;
     
      LastIndex := FEditHandles.Count - 1;
      H := HWND(NativeInt(FEditHandles[LastIndex]));
     
      // DestroyMyEdit renvoie True si l'instance a bien été trouvée et libérée
      if DestroyMyEdit(H) then
        FEditHandles.Delete(LastIndex)
      else
        Log.Lines.Add('Échec destruction edit');
    end;
     
    { WndProc override
      - intercepte les messages personnalisés dont les identifiants ont été
        obtenus via RegisterWindowMessage dans FormCreate
      - Msg.WParam contient le HWND de l'edit qui a généré l'événement
    }
    procedure TForm1.WndProc(var Msg: TMessage);
    begin
      if Msg.Msg = FMsgMyEditEnter then
      begin
        Log.Lines.Add('Received OnEnter from DLL, Edit HWND=' +
          IntToStr(Msg.WParam));
        Exit; // message traité, ne pas appeler inherited pour éviter double traitement
      end
      else if Msg.Msg = FMsgMyEditExit then
      begin
        Log.Lines.Add('Received OnExit from DLL, Edit HWND=' +
          IntToStr(Msg.WParam));
        Exit;
      end;
     
      // Tous les autres messages sont traités normalement par la VCL
      inherited;
    end;
     
    end.
    Nom : Video_2025_10_26-1_edit_0.gif
Affichages : 156
Taille : 398,3 Ko

  9. #9
    Membre éclairé

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    407
    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 : 407
    Billets dans le blog
    1
    Par défaut
    Merci, XeGregory ! C'est une piste intéressante. Je vais tester cela et voir comment je peux intégrer cela dans mon projet.

    Je te donnerai de nouvelles.

Discussions similaires

  1. Réponses: 12
    Dernier message: 17/03/2009, 12h51
  2. Réponses: 4
    Dernier message: 03/02/2009, 20h47
  3. Des soucis avec mon application Excel sur les contacts
    Par diddle dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 27/11/2007, 20h50
  4. Question Drag-Drop sur les TEdit qui m'aidez
    Par ghazaliove dans le forum Delphi
    Réponses: 1
    Dernier message: 01/11/2006, 09h46
  5. Réponses: 5
    Dernier message: 24/04/2005, 05h09

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