TListBox

Résolu
Signaler
Messages postés
49
Date d'inscription
lundi 27 janvier 2003
Statut
Membre
Dernière intervention
24 mars 2017
-
Messages postés
3826
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
10 mai 2021
-
Pour un composant (dérivé de TCustomPanel), j'avais besoin (entre autres) d'une TListBox. (Programme minimal ci-dessous)
MALHEUREUSEMENT, le composant étant déposé sur une forme, dans l'inspecteur d'objet, le clic sur le bouton "Items" rend le Message "Impossible d'affecter TListBoxStrings à TCPListBox" au lieu d'ouvrir la boite de saisie. De plus, impossible de déposer un autre composant sur la forme.
Quelqu'un a-t-il une idée pour me dépanner ? JE DESESPERE ...MERCI.
//====================================
unit U_TCPListBox;
interface//====================================
uses ExtCtrls,StdCtrls,Classes;
type
  TCPListBox = class(TCustomPanel)
    LB: TListBox;
  private
    procedure SetLBItems(value: TStrings);
    function GetLBItems: TStrings;
  protected
  public
    constructor Create(AOwner: TComponent); override;
  published
    property LBItems: TStrings read GetLBItems write SetLBItems;
  end;
  procedure Register;
implementation//==================================
constructor TCPListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  LB:= TListBox.Create(Self);
  with LB do begin
    Parent:= Self;
  end;
end;//-----------------------------------------
procedure TCPListBox.SetLBItems(value: TStrings);
begin
  LB.Items.Assign(value);
end;//-----------------------------------------
function TCPListBox.GetLBItems: TStrings;
begin
  Result.Assign(LB.Items);
end;//-----------------------------------------
procedure Register;
begin
  RegisterComponents('Exemples', [TCPListBox]);
end;//-----------------------------------------
end.

JLB

12 réponses

Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
unit CPLB;

interface

uses Windows, SysUtils, Classes, Controls, ExtCtrls, StdCtrls;

type
  TCPListBox = class(TCustomPanel)
  private
    fListBox: TListBox;
    procedure SetItems(value: TStrings);
    function GetItems: TStrings;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read GetItems write SetItems;
  end;

procedure Register; 

implementation

procedure Register;
begin
  RegisterComponents('Exemples', [TCPListBox]);
end;

constructor TCPListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fListBox := TListBox.Create(Self);
  fListBox.Parent := Self;
  fListBox.Align  := alClient;
end;

destructor TCPListBox.Destroy;
begin
  fListBox.Free;
  inherited Destroy;
end;

procedure TCPListBox.SetItems(value: TStrings);
begin
  fListBox.Items.Assign(value);
end;

function TCPListBox.GetItems: TStrings;
begin
  result := fListBox.Items;
end;

end.







<hr size="2" width="100%" />
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
3eme alternative et de loin la meilleure pour ce genre de composant a utiliser en mode Design :

unit CPLB;

interface

uses Windows, SysUtils, Classes, Controls, ExtCtrls, StdCtrls;

type
  TCPListBox = class(TCustomPanel)
  private
    fListBox: TListBox;
    procedure SetListBox(value: TListBox);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property ListBox: TListBox read fListBox write SetListBox;
    { on aura accés a toutes les propriétés et evenements de fListBox
      directement dans l'inspecteur d'objet, notement la propriété Items :) }
  end;

procedure Register; 

implementation

procedure Register;
begin
  RegisterComponents('Exemples', [TCPListBox]);
end;

constructor TCPListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fListBox := TListBox.Create(Self);
  fListBox.Parent := Self;
  fListBox.Align  := alClient;
end;

destructor TCPListBox.Destroy;
begin
  fListBox.Free;
  inherited Destroy;
end;

procedure TCPListBox.SetListBox(value: TListBox);
begin
  fListBox.Assign(value);
end;

end.

<hr size="2" width="100%" />
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
Derniers conseils :

- Une variable ou objet appartenant a un objet maitre doit figurer dans la zone Private. on evite dans la mesure du possible de fournir de tels elements dans d'autres zone (Public/Published/Protected) en dehors d'un accés par le mots reservé "Property".

- Les conventions d'ecriture de code veulent que les identifiants des elements privés, ci-dessus cités, prennent un "f" au debut de leur noms (sauf bien sur les fonction et procedure de definition/recuperation Set/Get, les messages systemes etc)

<hr size="2" width="100%" />
Messages postés
4297
Date d'inscription
samedi 19 janvier 2002
Statut
Modérateur
Dernière intervention
9 janvier 2013
31
Bonjour,

Explications en dessous du code
unit U_TCPListBox;

interface
//====================================
uses
  ExtCtrls, StdCtrls, Classes, Controls; //0

type
  TCPListBox = class(TCustomPanel)
  private
    procedure SetLBItems(value: TStrings);
    function GetLBItems: TStrings;
  protected
    LB: TListBox;        //1
  public
    constructor Create(AOwner: TComponent); override;
  published
    property LBItems: TStrings read GetLBItems write SetLBItems;
  end;
procedure Register;

implementation

constructor TCPListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent := AOwner as TWinControl; //2
  LB := TListBox.Create(AOwner);
  LB.Parent := Self;     //3
end;

procedure TCPListBox.SetLBItems(value: TStrings);
begin
  LB.Items.Assign(value);
end;

function TCPListBox.GetLBItems: TStrings;
begin
  //Result.Assign(LB.Items);
  Result := lb.Items;       //4
end;

procedure Register;
begin
  RegisterComponents('Exemples', [TCPListBox]);
end;

end.

Plusieurs erreurs :
0- inclusion de l'unité Controls car nous avons besoin d'affecter la propriété Parent de ton composant (de type TWinControl).

1- Le champ LB ne doit pas être déclaré avec la propriété Public par défaut car il devient alors accessible depuis l'extérieur sans passer par les accesseurs en lecture comme en écriture. Ici, je l'ai mis en portée protected. Au pire, imagine que le code, quelque part ailleurs détruire ta liste. Comment réagira alors le code de ton composant ?

2- Tout composant visuel a besoin d'avoir sa propriété Parent affectée pour que ses coordonnées soient relatives à son parent et non à la fiche.

3- même remarque que ci-dessus.

4- Là, l'erreur était manifeste et le compilateur renvoyait un message d'avertissement assez explicite :[Avertissement] U_TCPListBox.pas(39): La valeur de retour de la fonction 'TCPListBox.GetLBItems' peut être indéfinie
En effet, si Result est du type TStrings, tu ne créais pas d'objet de type TStrings, donc tu appelais la méthode Assign d'un objet égal à NIL. Mais pourquoi créer un objet liste de chaînes puisque tu disposes déjà de cette structure (membre LB).
Pense à activer les contrôles du compilateur (Projet/Options/Messages du compilateur).

Voilà pour les commentaires et remarques. En espérant que cela t'aidera à y voir plus clair à l'avenir.
Bonne prog' et...

May Delphi be with you !
<hr color="#008000" />Pensez à cliquer sur Réponse acceptée lorsque la réponse vous convient.
Messages postés
4297
Date d'inscription
samedi 19 janvier 2002
Statut
Modérateur
Dernière intervention
9 janvier 2013
31
@cirec : tu as tout à fait raison de faire remarquer cette lacune. Et je ne me suis jamais posé la question, faisant toujours bien attention de transmettre une référence valide en paramètre. J'ai donc remonté la hiérarchie des composants jusqu'à TControl et voici ce que nous dit son constructeur :
constructor TComponent.Create(AOwner: TComponent);
begin
  FComponentStyle := [csInheritable];
  if AOwner <> nil then AOwner.InsertComponent(Self);
end;
Eh bien, si AOwner vaut nil, il ne se passe rien. Et pourtant, en créant un composant dynamiquement avec nil comme argument, le composant visuel apparait bien sur la fiche.Seul bémol, comme la fiche ne possède pas ce composant créé dynamiquement, il faudra procéder à sa destruction manuellement. Un peu comme on le fait pour les composants non visuels, genre TStringList.

"Le Owner est celui qui se charge de détruire le composant à la fin ce
n'est pas forcément le parent (sauf quand il est déposer depuis l'IDE).
Autrement dit le Owner peut être différent du parent"
Seul le propriétaire est responsable de la destruction des composants qu'il détient. Le parent n'est là que pour permettre un positionnement correct d'un composant visuel (on ne positionne jamais les composants en coordonnées absolues d'écran, heureusement ).

Est-ce mieux ainsi ?

May Delphi be with you !
<hr color="#008000" />Pensez à cliquer sur Réponse acceptée lorsque la réponse vous convient.
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
Pas de destructeur  = Memory leak LB n'est jamais detruit!

Result.Assign(LB.Items); < Result n'est pas une reference d'objet valide, de plus ce n'est pas la bonne methode pour faire cela :

Result := fListBox.Items; < on passe la reference de fListBox.Items au retour de fonction.

Alternative :

unit CPLB;

interface

uses Windows, SysUtils, Classes, Controls, ExtCtrls, StdCtrls;

type
  TCPListBox = class(TCustomPanel)
  private
    fListBox: TListBox;
    fStrings: TStrings;
    procedure SetStrings(value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read fStrings write SetStrings;
  end;

procedure Register; 

implementation

procedure Register;
begin
  RegisterComponents('Exemples', [TCPListBox]);
end;

constructor TCPListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fListBox := TListBox.Create(Self);
  fListBox.Parent := Self;
  fListBox.Align  := alClient;
  fStrings := fListBox.Items; // on passe la reference de fListBox.Items
                              // a fStrings
end;

destructor TCPListBox.Destroy;
begin
  fListBox.Free;
  inherited Destroy;
end;

procedure TCPListBox.SetStrings(value: TStrings);
begin
  fStrings.Assign(value); // On travail directement avec fStrings
                          // et plus fListBox
end;

end.

<hr size="2" width="100%" />
Messages postés
49
Date d'inscription
lundi 27 janvier 2003
Statut
Membre
Dernière intervention
24 mars 2017

Bonjour à vous qui m'avez dépanné,

Merci à Foxi pour tes réponses et le temps que tu y a passé.
L'absence du Destructeur était du au fait que je n'avais mis que ce  qui riquait d'être en rapport avec mon problème.


Par contre, même avec de l'humour, je trouve ta petite animation pas trés gentille.


Merci à DelphiProg pour ta réponse trés concise et trés explicite.

Pour le point //1 je pensais que c'était correct. Certainement que plusieurs de mes problèmes proviennent de cette erreur.
Pour le point //2, je pensais (bêtement, sans y avoir vraiment réfléchi ni vérifié) que celà était fait par inherited Create()
Et bizarrement j'avais bien mis cette ligne pour le point //3 ? (je suis incohérent avec //2)
Point //4 c'est pourtant simple !!! Merci

Je vais pouvoir finir mon composant et je le publerai sur le site (bien qu'en cherchant à me dépanner, avant de poser ma question, je suis tombé sur des programmes qui y ressemblent un peu ... mais ne sont pas des composants (et c'est là que les difficultés apparaissent)

JLB
Messages postés
3826
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
10 mai 2021
46
@Delphiprog:

pour le point N°2 que se passe t'il si je crée le composant dynamiquement avec Nil comme AOwner ?

Normalement c'est l'IDE qui se charge de lui affecter un parent au moment ou l'on dépose le composant sur la fiche. Si il est créer manuellement il faut lui affecter un parent manuellement aussi.

Le Owner est celui qui se charge de détruire le composant à la fin ce n'est pas forcément le parent (sauf quand il est déposer depuis l'IDE).
Autrement dit le Owner peut être différent du parent

qu'en penses-tu ?

 
@+
Cirec

<hr siz="" />
Messages postés
49
Date d'inscription
lundi 27 janvier 2003
Statut
Membre
Dernière intervention
24 mars 2017

Je constate que ma question a finalement servie à présiser plus clairement dans notre pensée des points qui nous apparaissent aller de soi et qui finalement ont besoin d'être réfléchis "consciemment".
Merci à tous

JLB
Messages postés
3826
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
10 mai 2021
46
Bon  ... je m'aperçois que je me suis mal exprimé ^^

tes explications sont juste et heureusement qu'il se comporte comme ça
mais c'est pas là ou je voulais en venir ...

ce que je voulais dire c'est que dans l'implémentation du constructeur la ligne :
  Parent := AOwner as TWinControl; //2
est totalement inutile .... que tu passes au constructeur (Nil) ou (Form1), " en dynamique bien sur ", il faudra quand même définir le parent manuellement ... et si le composant est installé, et qu'il est déposé sur la fiche depuis la palette, le parent est affecté automatiquement par Delphi.

"... Et pourtant, en créant un composant dynamiquement avec nil comme argument, le composant visuel apparait bien sur la fiche.  ..."
ben chez moi non essaye ce code et tu verras (D7 Perso)

Var CPListBox : TCPListBox;
Begin
  CPListBox : = TCPListBox.Create(Form1);

  With CPListBox Do

  Begin
    Parent := Self; // si tu
mets cette ligne en commentaire il ne s'affiche pas ^^ et avec Create(Nil) c'est pareil
    Top : = 10;

    Left := 10;
   End ;
End;

Et pour le troisième point c'est exactement ce que je disais
Owner == Propriétaire

Par contre toi tu essaye d'affecter Owner au parent et c'est là que ça ne me convient pas puisque Owner peut être d'un autre type que TWinControl

 
@+
Cirec

<hr siz="" />
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
CPListBox := TCPListBox.Create(Form1);

RHAAAAAAAAA!


CPListBox : = TCPListBox.Create(SELF);

<hr size="2" width="100%" />
Messages postés
3826
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
10 mai 2021
46
On s'en fout royalement


...
c'était juste pour l'exemple comme le code était sorti du contexte ...
Self ici (dans ce bout de code) ne représente rien et donc pour éviter les confusions j'ai mis Form1 (qui me paraissait plus explicite)

 Mais c'est pas ça le plus important ...
étonnant d'ailleurs que tu n'aies pas remarqué ça

 
@+
Cirec

<hr siz="" />