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 :

Collections et héritage de fiche


Sujet :

Composants VCL Delphi

  1. #1
    Membre chevronné

    Profil pro
    Inscrit en
    Novembre 2007
    Messages
    1 519
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Novembre 2007
    Messages : 1 519
    Points : 2 153
    Points
    2 153
    Billets dans le blog
    1
    Par défaut Collections et héritage de fiche
    Bonsoir à tous,

    je voudrais vous exposer ici un problème que je rencontre depuis longtemps avec les collections lorsque intervient un héritage de fiche. Ce problème est d'ailleurs spécifique à l'héritage de fiche, en effet sur une fiche simple aucun soucis.

    Imaginons un composant TMonComposant ce dernier possède une collection de type TOwnedCollection (pour avoir accès au mode conception) et chaque item de la collection est un TMonCollectionItem dérivant de TCollectionItem.

    Ok je recense le composant et je le dépose sur une fiche, pas de problèmes je peux rajouter de nouveaux items, les supprimer, passer en mode texte (ALT + F12) tout est normal.

    Maintenant imaginons que je fasse un héritage de cette fiche qui contient mon composant. Ok lorsque je regarde les propriétés via l'inspecteur d'objet tout va bien mais dès que j'essaye d'afficher la fiche en mode texte patatras :

    Impossible d'affecter TMonCollectionItem à TMonCollectionItem.

    Ceci vient du fait que la méthode Assign du TMonCollectionItem n'est pas redéfinie. Qu'à cela ne tienne, je redéfinie Assign et je fais le transfert des propriétés publiées de source vers moi-même. Je recompile mon composant, ça marche pour les types simples plus de plantages quand je passe en mode texte.

    Par contre ça se complique lorsque l'une des propriétés publiées est un objet. Là si je fais une affectation directe de l'objet contenu dans source vers cible j'obtiens un résultat plus qu'étrange à savoir le nom de l'objet se retrouve préfixé par le nom de la fiche parente (du style Form1.Edit1 par exemple). Ceci est assez problématique et a tendance à faire planter des traitements qui voudrait utiliser cet objet via le TCollectionItem. J'ai essayé de mettre un Setter sur la propriété Collection de mon composant est de faire un Assign entre les deux instances plutôt qu'une affectation directe mais aucune différence.

    Après moultes péripéties j'ai finalement trouvé la solution de faire en sorte, au moment de l'Assign, de retrouver la fiche Form2 et de faire un FindComponent pour récupérer le vrai objet sur Form2. Avec cette solution plus de problème, l'objet n'apparait plus avec un préfixe ou quoi que ce soit. Cependant je trouve cela extrêmement lourd ! Aussi peut-être ai-je loupé quelque chose que des gens plus habitué aux subtilités du mode conception sauront détecter.

    Voici un exemple de code de ma collection en question pour ceux qui veulent jeter un oeil.

    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
    unit Collection_Unit;
     
    interface
    uses Classes;
     
    type
      TMonCollectionItem = class( TCollectionItem )
      protected
       FNom: String;
       FCompo: TComponent;
       FFormOwner: TComponent;
       function GetFormOwner: TComponent;
       function FindComponent( ASourceComponent: TComponent ): TComponent;
       property FormOwner: TComponent read GetFormOwner;
      public
       Constructor Create(Collection: TCollection); override;
     
       procedure Assign(Source: TPersistent); override;
      published
       property Nom: String read FNom write FNom;
       property Compo: TComponent read FCompo write FCompo;
      end;
     
      TMonComposant = class( TComponent )
      private
        FMaCollection: TOwnedCollection;
        procedure SetMaCollection(const Value: TOwnedCollection);
      public
       Constructor Create( AOwner: TComponent ); override;
       Destructor Destroy; override;
     
      published
       property MaCollection: TOwnedCollection read FMaCollection write SetMaCollection;
      end;
     
    procedure Register;
     
    implementation
    uses Forms;
     
    procedure Register;
    begin
      RegisterComponents( 'Samples', [TMonComposant] );
    end;
     
    { TMonComposant }
     
    constructor TMonComposant.Create(AOwner: TComponent);
    begin
      inherited;
      FMaCollection := TOwnedCollection.Create( Self, TMonCollectionItem );
    end;
     
    destructor TMonComposant.Destroy;
    begin
      FMaCollection.Free;
      inherited;
    end;
     
    procedure TMonComposant.SetMaCollection(const Value: TOwnedCollection);
    begin
      FMaCollection.Assign( Value );
    end;
     
    { TMonCollectionItem }
     
    procedure TMonCollectionItem.Assign(Source: TPersistent);
    begin
      if ( Source is TMonCollectionItem ) then
      begin
       FNom := TMonCollectionItem( Source ).Nom; 
       FCompo := FindComponent( TMonCollectionItem( Source ).Compo );
      end
      else
       inherited;
     
    end;
     
    constructor TMonCollectionItem.Create(Collection: TCollection);
    begin
      inherited;
      FFormOwner := NIL;
    end;
     
    function TMonCollectionItem.FindComponent(
      ASourceComponent: TComponent): TComponent;
    begin
      Result := NIL;
      if Assigned( FormOwner ) then
        Result := FormOwner.FindComponent( ASourceComponent.Name );
    end;
     
    function TMonCollectionItem.GetFormOwner: TComponent;
    var
     CurrentOwner: TComponent;
    begin
      if not Assigned( FFormOwner ) then
      begin
        // partir du composant qui contient la collection
        CurrentOwner := TComponent( TOwnedCollection( Collection ).Owner );
        while ( CurrentOwner <> NIL ) and not CurrentOwner.InheritsFrom( TForm ) do
          CurrentOwner := CurrentOwner.Owner;
     
        FFormOwner := CurrentOwner;
      end;
     
      Result := FFormOwner;
    end;
     
    end.
    Voilà c'est à vous, merci d'avance.
    La FAQ - les Tutoriels - Le guide du développeur Delphi devant un problème

    Pas de sollicitations techniques par MP -

  2. #2
    Membre à l'essai
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2005
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2005
    Messages : 15
    Points : 12
    Points
    12
    Par défaut
    Bonjour,

    Ma réponse arrive peu-être un peu tard, mais c'est en cherchant un exemple de code pour créer un composant possédant une collection éditable dans l'EDI que je suis tombé sur votre message...

    Pour ma part, je suis habitué à créer des composants qui possèdent des références sur d'autres composants (mais pas des collections), et je pense utile de vous apporter quelques précisions à ce sujet.

    Donc dans le cas simple d'un TcomposantA qui possède une référence sur un TComposantB, on déclare simplement

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    TComposantA = class(TComponent)
    private
      FcompoB : TComposantB;
    public
      CompoB : TComposantB read FCompoB write FcompoB;
    end;
    Inutile d'écrire un attribut supplémentaire pour stocker son Owner.
    Enuite, lorsqu'on observe la fiche en mode texte, si le compoB a le même owner que le composantA, alors la propriété publiée s'écrit simplement
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CompoB = NomDuComposantB
    tandis que si leur owner est différent (composant B sur une autre fiche par exemple), on lit alors
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CompoB = NomDuOwnerDeB.NomDuComposantB
    Je pense donc que c'est ce principe qui vous a surpris, mais c'est le fonctionnement normal de Delphi, et c'est ce qui lui permet de recharger correctement le bon pointeur dans l'attribut FCompoB à la relecture du dfm.

    Donc si vous utilisez simplement le code suivant je pense que ça devrait marcher :
    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
    unit Collection_Unit;
     
    interface
    uses Classes;
     
    type
      TMonCollectionItem = class( TCollectionItem )
      protected
       FNom: String;
       FCompo: TComponent;
      public
       procedure Assign(Source: TPersistent); override;
      published
       property Nom: String read FNom write FNom;
       property Compo: TComponent read FCompo write FCompo;
      end;
     
      TMonComposant = class( TComponent )
      private
        FMaCollection: TOwnedCollection;
        procedure SetMaCollection(const Value: TOwnedCollection);
      public
       Constructor Create( AOwner: TComponent ); override;
       Destructor Destroy; override;
     
      published
       property MaCollection: TOwnedCollection read FMaCollection write SetMaCollection;
      end;
     
    procedure Register;
     
    implementation
    uses Forms;
     
    procedure Register;
    begin
      RegisterComponents( 'Samples', [TMonComposant] );
    end;
     
    { TMonComposant }
     
    constructor TMonComposant.Create(AOwner: TComponent);
    begin
      inherited;
      FMaCollection := TOwnedCollection.Create( Self, TMonCollectionItem );
    end;
     
    destructor TMonComposant.Destroy;
    begin
      FMaCollection.Free;
      inherited;
    end;
     
    procedure TMonComposant.SetMaCollection(const Value: TOwnedCollection);
    begin
      FMaCollection.Assign( Value );
    end;
     
    { TMonCollectionItem }
     
    procedure TMonCollectionItem.Assign(Source: TPersistent);
    begin
      if ( Source is TMonCollectionItem ) then
      begin
       FNom := TMonCollectionItem( Source ).Nom;
       FCompo := TMonCollectionItem( Source ).Compo ;
      end
      else
       inherited;
     
    end;
     
    end.
    Attention toutefois, il y a peut-être une subtilité avec TMonComposant.SetMaCollection, comme avec les TStrings, mais je ne l'ai plus en tête. Alors comme ceci correspond exactement à ce que j'ai besoin de faire, je teste dès que possible et je vous tiens au courant !

    Cordialement.

  3. #3
    Membre à l'essai
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2005
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2005
    Messages : 15
    Points : 12
    Points
    12
    Par défaut
    Re-bonjour,

    Après une deuxième relecture, je me suis rendu compte que j'avais fait abstraction du problème de l'héritage dans ma première réponse, et surtout du comportement que vous auriez souhaité dans le cadre de cet héritage.

    Ce qu'il faut comprendre dans la version basique que je vous ai suggéré :
    Nous allons supposer que vous placez un TMonComposant sur une form1, que ce monComposant1 contient un seul item dans sa collection, qui lui-même pointe vers un composant2 de la form1 (Form1.monComposant1.maCollection[0].compo:=form1.composant2).
    Alors il faut bien avoir conscience que form1.monComposant1.maCollection[0].compo pointe vers un objet, et que cet objet a été construit dans l'EDI à la conception (ici c'est l'objet Form1.composant2, mais il pourrait se trouver sur une autre fiche). Je veux dire par là que l'attribut FCompo est un simple pointeur sur un objet (un composant en l'occurence), et que pour l'item, ce pointeur n'est pas synomyme de "owner.owner.composant2".

    Pour illustrer ces propos, supposons maintenant que je fasse pointer monComposant1.maCollection[0].compo vers une MonAutreFiche1.composantX. Au designTime c'est parce que cette fiche est chargée dans l'EDI que le pointeur est valide, tandis qu'au runtime le pointeur ne sera valide que si MonAutreFiche1 est construite (celle proposée en construction automatique dans les options du projet, pas une autre du même type mais avec un nom différent). Et il n'est pas question d'aller chercher le composantX sur la Form1 !

    Revenons maintenant au problème de l'héritage :
    Supposons que vous écriviez une classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    TForm2 = class(TForm1)
    //...
    end;
    Tant que vous ne modifiez rien dans l'inspecteur d'objet sur la Form2, les propriétés publiées de monComposant1 ne sont pas modifiées dans la fiche Form2 (j'ai envie de dire "leurs valeurs ne sont pas surchargées" mais c'est un abus de langage). Alors l'EDI gère ça "très bien", puisqu'il s'arrange pour que Form2.monComposant1.maCollection[0].compo pointe vers le même objet que Form1.monComposant1.maCollection[0].compo.
    C'est plutôt une bonne chose si on considère que cet objet peut potentiellement être sur une autre fiche qui n'a rien à voir avec ces deux là.
    Mais dans notre cas, cela veut dire que les 2 monComposant1.maCollection[0].compo (celui de la Form1 et celui de la form2) pointent vers le même objet : Form1.Composant2.
    C'est normal et c'est voulu !

    Mais je comprends que celà puise vous surprendre, voir vous poser des problèmes...

    Pour rester sur des comportements standards, je pense que le mieux est de le savoir, et de faire attention à modifier, avec l'inspecteur d'objet, sur la fiche Form2, l'objet pointé par monComposant1.maCollection[0].compo, afin de le "re-renseigner" avec la valeur Form2.composant2 (qui du coup sera stocké dans le DFM sous la forme simple "compo = Composant2").
    C'est à mon avis beaucoup moins dangereux pour la compréhension de votre code, plutôt que de chercher à modifier la gestion des références qu'impose l'EDI de Delphi, qui somme toutes est plutôt bien faite pour le cas général.

    Sinon je n'ai pas encore implémenté mon composant avec collection, du coup je n'ai pas encore de réponse à propos de la subtilité dont j'ai parlé sur la méthode TMonComposant.SetMaCollection, mais je vous tiendrai au courant.

    Cordialement.

  4. #4
    Membre à l'essai
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2005
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2005
    Messages : 15
    Points : 12
    Points
    12
    Par défaut
    Re-Re Bonjour !

    J'ai maintenant implémenté mom propre composant avec une propriété de type TOwnedCollection, et j'ai pu tester en détail vos cas d'héritage.

    Je confirme donc tout ce que j'ai dit dans les précédents messages.

    De plus, je précise que votre méthode qui consiste à s'arranger pour que les items de la collection de la fiche fille pointent vers les objets de la fiche fille (au lieu de la fiche mère), ne marchera que si les objets pointés sont directement possédés par la fiche, et pas par un composant conteneur qui serait sur la fiche. ça je ne l'ai pas testé, mais ça me semble évident au vu de votre 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
    function TMonCollectionItem.GetFormOwner: TComponent;
    var
     CurrentOwner: TComponent;
    begin
      if not Assigned( FFormOwner ) then
      begin
        // partir du composant qui contient la collection
        CurrentOwner := TComponent( TOwnedCollection( Collection ).Owner );
        while ( CurrentOwner <> NIL ) and not CurrentOwner.InheritsFrom( TForm ) do
          CurrentOwner := CurrentOwner.Owner;
     
        FFormOwner := CurrentOwner;
      end;
     
      Result := FFormOwner;
    end;
    La partie ci-dessus remonte bien jusqu'à la fiche "racine",
    mais la partie suivante, qui utilise FindComponent, ne fait pas une recherche en profondeur :
    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
    procedure TMonCollectionItem.Assign(Source: TPersistent);
    begin
      if ( Source is TMonCollectionItem ) then
      begin
       FNom := TMonCollectionItem( Source ).Nom; 
       FCompo := FindComponent( TMonCollectionItem( Source ).Compo );
      end
      else
       inherited;
     
    end;
     
    function TMonCollectionItem.FindComponent(
      ASourceComponent: TComponent): TComponent;
    begin
      Result := NIL;
      if Assigned( FormOwner ) then
        Result := FormOwner.FindComponent( ASourceComponent.Name );
    end;
    Et de toute façon, inutile de tenter de faire simplement une recherche en profondeur, car plusieurs composants peuvent contenir les mêmes "sous-composants"...

    Donc si vous vouliez que ça marche à tous les coups, il faudrait mémoriser une "pile de Owner.name" dans la méthode getFormOwner, puis dépiler dans la méthode findComponent afin de retrouver le "chemin d'accès" à votre composant depuis la fiche fille.
    ça marcherait, mais aux prix d'efforts importants, comparé à la simplicité de réaffecter la valeur directement dans l'inspecteur sur la fiche fille...

    Pour terminer, voici la subtilité dont je parlais sur la méthode SetMaCollection : généralement on écrit plutôt :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    procedure TMonComposant.SetMaCollection(const Value: TOwnedCollection);
    begin
      if assigned(FMaCollection) then
         FMaCollection.Assign( Value )
      else 
         FMaCollection:=Value;
    end;
    Comme FMaCollection est affecté à la construction, j'avoue que je n'en voit pas l'intérêt, mais il me semble que sur les TStrings, si on ne respecte pas ce modèle, ça a tendance à créer des plantages sous l'EDI. Ne me demandez pas pourquoi...

    Cordialement.

Discussions similaires

  1. Réponses: 13
    Dernier message: 15/10/2012, 11h43
  2. Collections et héritage
    Par kodo dans le forum Langage
    Réponses: 5
    Dernier message: 17/05/2011, 11h25
  3. Relation inverse vers une collection avec héritage
    Par Julienoune dans le forum Hibernate
    Réponses: 0
    Dernier message: 18/03/2009, 06h09
  4. Collection et héritage
    Par BigNic dans le forum C#
    Réponses: 7
    Dernier message: 25/01/2008, 16h17
  5. Héritage et Iterateur de Collection
    Par onur dans le forum C++
    Réponses: 6
    Dernier message: 12/04/2006, 00h05

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