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

C++Builder Discussion :

Parent d'un sous composant


Sujet :

C++Builder

  1. #1
    Invité
    Invité(e)
    Par défaut Parent d'un sous composant
    Bonjour,

    Je suis en train de définir un composant utilisateur qui ressemble un peu à un TComboBox, et est composé de 2 parties :

    1- un TEdit, toujours visible
    2- un TListBox, qui est affiché en dessous sous certaines conditions.

    Je fais descendre le composant de TEdit, et lui associe un TListBox... Ca donne donc un truc comme ca...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    class PACKAGE TMLLookup : public TEdit
    {
    private:
    	TListBox *FAutolist;
    __published:
      __property TListBox *AutoList = {read=FAutolist,write=FAutolist};
    };
    avec dans le constructeur :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    __fastcall TMLLookup::TMLLookup(TComponent* Owner)
    	: TEdit(Owner)
    {
    AutoList=new TListBox(this);
    AutoList->Parent=this;
    AutoList->Visible=false;
    AutoList->Width=Width;
    AutoList->Top=Height;
    }
    Et, pour afficher ou cacher la liste, je faire Autolist->Show() ou Hide() et hop !

    Oui mais voila, quand je veux afficher le TListBox, comme il doit apparaitre en dessous du TEdit, il est en dehors de la zone d'affichage de son Parent (le TEdit), et donc, il n'est pas visible...

    Une solution naive serait de tout faire dépendre d'un TPanel qui aurait la bonne taille. Mais du coup, il faudrait réserver la place d'affichage de la ListBox, même quand elle est invisible. Ca gaspille pas mal de place sur l'écran.

    J'ai trouvé une bidouille : lors de l'affichage de la TListBox, je modifie son Parent au vol, et l'affecte au Parent de son parent...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    AutoList->Parent=Parent;
    AutoList->Left=Left;
    AutoList->Width=Width;
    AutoList->Top=Top+Height;
    AutoList->Show();
    Et je la remet à sa place quand on la cache. Mais ce n'est pas très élégant, et si le parent n'a pas la place d'afficher l'Autolist, je suis ramené au problème précédent...

    Y a-t-il une façon propre de permettre à un sous composant de s'afficher en dehors de la zone d'affichage de son parent?

    Francois

  2. #2
    Invité
    Invité(e)
    Par défaut
    Après quelques essais, j'arrive à quelque chose comme çà. A l'exécution (une fois que le composant est sur une TForm), je recherche le "parent de plus haut niveau (c'est à dire la TForm ancêtre), et je lui affecte ma liste... Puis je place le composant dans le nouveau système de coordonnées...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if(AutoList->Parent==this) {
       // recherche du parent le plus haut niveau
    	TWinControl *p=Parent;
    	while(p->Parent!=NULL) p=p->Parent;
      // coordonnées de l'autolist dans le nouveau systeme
    	TPoint pt=AutoList->ClientToParent(TPoint(0,0),p);
      // affecte le parent
    	AutoList->Parent=p;
      // affecte les coordonnees
    	AutoList->Left=pt.x;
    	AutoList->Top=pt.y;
    }
    Il est probablement prudent de défaire cette affectation quand on cache la liste, si pour une raison (utilisation de TFrames par exemple), la TForm parente est détruite avant la liste, on va se retrouver avec un célèbre message (Control sans Parent...)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    AutoList->Parent=this;
    AutoList->Left=0;
    AutoList->Top=Height;
    AutoList->Hide();
    Ca fait ce que je veux, ca semble tourner correctement, mais ce n'est pas d'une grande élégance. Quelqu'un connait un moyen propre de faire cela?

    Francois

  3. #3
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2002
    Messages
    1 407
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 1 407
    Par défaut
    Salut !

    Pour quelle raison as-tu besoin de TEdit + ListBox à la place de TComboBox ?

    Peut-être qu'il faut procéder autrement et en particulier : n'instancier la listbox que pour la dérouler.
    Donc on a tout sous la main pour la positionner à ce moment là.
    Il est alors facile de la détruire en lieu et place d'un Hide().

    Donc une TStringList pour mémoriser les items de cette listbox !

    C'est à tester !

    A plus !

  4. #4
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par henderson Voir le message
    Pour quelle raison as-tu besoin de TEdit + ListBox à la place de TComboBox ?
    En fait, il ne s'agit vraiment pas d'une combobox, dans le sens où la liste est vide en général. Le composant sert à faire des recherches dans des lignes de tableaux. Quand je tape des caractères dans l'Edit, il m'affiche toutes les lignes contenant ces mots dans la listbox. Si je clique sur une, ca m'y emmene, sinon, je peux continuer à taper (et la liste se réduit), ou cliquer ailleurs (et elle se ferme).

    Auparavant, j'avais deux composants dans mes TForm, une Edit, et un Listbox, et tout allait bien. C'est au moment où je veux en faire un Composant que cela se complique.

    L'intérêt du composant, c'est qu'on lui donne deux évènements : LoadList() (appelé quand la liste va se dérouler) et ClickList() qu'il suffit de surcharger pour pouvoir utiliser ce type de "lookup" partout où l'on veut. C'est très adapté à devenir un composant. Le seul défaut, c'est cette histoire d'affichage : la nécessité pour la liste d'avoir un parent "plus grand" que l'Edit.

    A plus long terme, en ajoutant une meilleure gestion de la capture de souris (quand la liste est déroulée), on pourrait généraliser cela à toutes sortes de composants "à la Combobox" : des boutons ouvrant des checklistbox, des combobox avec des Treeview dedans, des popupmenus, n'importe quoi.

    Toutes ces listes se ramèneraient à 2 ou 3 fonctions d'interface :
    1- le chargement de la liste popup
    2- la gestion des clics sur la liste (evt des doubleclics)
    3- la fermeture de la liste sans clic

    C'est une sorte de popup généralisé, si tu veux.

    Citation Envoyé par henderson Voir le message
    Peut-être qu'il faut procéder autrement et en particulier : n'instancier la listbox que pour la dérouler.
    Donc on a tout sous la main pour la positionner à ce moment là.
    Il est alors facile de la détruire en lieu et place d'un Hide().
    Je vais regarder cela. Mais la question de son Parent demeure, à moins de mettre l'instanciation de la ListBox dans le code utilisateur, ce qui me parait un peu sale.

    Francois

  5. #5
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2002
    Messages
    1 407
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 1 407
    Par défaut
    Salut !

    Disons que ça pourrait ressembler à ceci (donc c'est une réflexion avant ton dernier message) :

    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
     
    class jEdit : public TEdit
    {
    private :
    TListBox *ListBox;
    TStringList *StringList;
        void __fastcall SetListVisible(bool Value);
        bool __fastcall GetListVisible();
     
    public :
        __fastcall jEdit(TComponent *AOwner);
        __fastcall ~jEdit();
     
    __property bool ListVisible={read=GetListVisible, write=SetListVisible};
    };

    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
     
    //---------------------------------------------------------------------------
    __fastcall jEdit::jEdit(TComponent *AOwner)
        : TEdit(AOwner)
    {
    StringList = new TStringList;
    // pour lui donner vie
    StringList->Add("Coucou 1");
    StringList->Add("Coucou 2");
    StringList->Add("Coucou 3");
    StringList->Add("Coucou 4");
     
    if(AOwner->InheritsFrom(__classid(TWinControl)))
        {
        Parent = (TWinControl*)AOwner;
        }
    ListBox = NULL;
    }
    //---
    __fastcall jEdit::~jEdit()
    {
    delete StringList;
    }
    //---
     
    void __fastcall jEdit::SetListVisible(bool Value)
    {
    if(ListBox == NULL)
        {
        if(Value == true)
            {
            ListBox = new TListBox(this);
            ListBox->Parent = Parent;
            ListBox->Visible = false;
            for(int j=0; j < StringList->Count; j++)
                {
                ListBox->Items->Add(StringList->Strings[j]);
                }
            ListBox->Left = Left;
            ListBox->Top = Top + Height;
     
            TWinControl *P = Parent;
            while(P->InheritsFrom(__classid(TForm)) == false)
                {
                ListBox->Left = ListBox->Left + P->Left;
                ListBox->Top  = ListBox->Top + P->Top;
                P = P->Parent;
                }
            ListBox->Parent = P;
            ListBox->Visible = true;
            ListBox->BringToFront();
            }
        }
    else
        {
        if(Value == false)
            {
            delete ListBox;
            ListBox = NULL;
            }
        }
    }
    //---
    bool __fastcall jEdit::GetListVisible()
    {
    return (ListBox != NULL );
    }
    //---

    Donc j'ai testé en plaçant deux TPanel : Panel2 sur Panel1 sur la form.

    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
     
     
    jEdit *Edit;
     
    //---
    __fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
    {
    Edit = new jEdit(this);
    Edit->Parent = Panel2;
    }
    //---
    void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
    {
    Edit->ListVisible = !Edit->ListVisible;    
    }
    //---
    A plus !

  6. #6
    Invité
    Invité(e)
    Par défaut
    Resalut!

    Merci beaucoup pour le code, j'y vois plus clair maintenant...

    En fait, on est probablement obligés d'itérer, à l'exécution, le long de la hierarchie des parents, pour trouver celui de plus haut niveau.

    Tu passes pas la hiérarchie de classes, ce que je trouve assez malin (la seule critique que je ferais, c'est qu'il vaudrait mieux mettre TCustomForm que TForm, parce que TForm peut ne pas être dans la hiérarchie, et là, le mec qui débugue va pleurer sa mère...)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    TWinControl *P = Parent;
            while(P->InheritsFrom(__classid(TForm)) == false)
                {
                ListBox->Left = ListBox->Left + P->Left;
                ListBox->Top  = ListBox->Top + P->Top;
                P = P->Parent;
                }
    J'utilisais une autre propriété : le fait que la "forme mère" est celle dont le parent est NULL. Voila mon code de production actuel :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    TWinControl *p=Parent;
    while(p->Parent!=NULL) p=p->Parent;
    TPoint pt=AutoList->ClientToParent(TPoint(0,0),p);
    AutoList->Parent=p;
    AutoList->Left=pt.x;
    AutoList->Top=pt.y;
    Mais en te lisant, je crois avoir trouvé la "bonne solution"...

    TScreen contient une propriété ActiveCustomForm, qui contient... exactement ce qu'on cherche !

    Donc,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    TPoint pt=AutoList->ClientToParent(TPoint(0,0),Screen->ActiveCustomForm);
    AutoList->Parent=Screen->ActiveCustomForm;
    AutoList->Left=pt.x;
    AutoList->Top=pt.y;
    Y'a pas, le framework Borland, c'est quand même un sacré truc !

    Francois

  7. #7
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2002
    Messages
    1 407
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 1 407
    Par défaut
    Salut !

    Ca prend forme !

    Je ne me souvenais plus du ActiveCustomForm.
    Par contre ... je préfère rechercher son parent-racine ... plutôt que passer par TScreen::ActiveCustomForm.
    Parce que le lien entre ActiveCustomForm et le parent-racine peut ne pas être le même (il pourrait arriver que ... ).
    Et surtout ne pas le faire dans le constructeur (instanciation depuis une form qui a le focus pour une form que ne l'a pas encore) !!!

    Ceci dit ... un utilisateur trouvera toujours des raisons pour pleurer !

    A plus !

  8. #8
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2002
    Messages
    1 407
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 1 407
    Par défaut
    Salut !

    Pour illustrer : l'utisateur a un bouton sur sa form principale.
    Quand il clique sur le bouton, il crée une form à lui et lui associe un objet :

    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
     
    class jMyForm : public TCustomForm
    {
    public :
        __fastcall jMyForm(TComponent *AOwner);
        __fastcall ~jMyForm();
    };
     
    __fastcall jMyForm::jMyForm(TComponent *AOwner)
        : TCustomForm(AOwner,0)
    {
    }
    __fastcall jMyForm::~jMyForm()
    {
    }
    Donc il clique, Form1 est alors active :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
    {
    TCustomForm *F = new jMyForm(Owner);
    TEdit *E = new TEdit(F);
    E->Parent = Screen->ActiveCustomForm;
    F->Show();
    }
    Donc imaginons que le TEdit affecte un Parent dans son constructeur à la listbox ...
    Nous ... on va en rire ... mais l'utilisateur ... c'est pas sûr !

    A plus !

  9. #9
    Invité
    Invité(e)
    Par défaut
    Bien vu!

    En fait, je n'affecte Parent à la "forme la plus haute" que quand je l'affiche (dans le keyup de l'Edit, dans mon cas). Du coup, comme on est en train de saisir dans Edit, il est actif. Si on voulait la ceinture ET les bretelles, on pourrait vérifier que Edit a le focus ce qui garantit que sa forme parente soit l'ActiveCustomForm.

    Aussi, je remets le Parent au Edit quand je cache le ListBox. Sinon, on court toujours le risque d'un 'Control has no Parent' lors de la fermeture de la forme (la VCL suit parfois des ordres de destruction étranges)

    Maintenant, un cas qui serait intéressant à suivre serait celui d'une application MDI. C'est quoi l'ActiveCustomForm? Si c'est le parent MDI, je pense que tout va bien, sinon...

    Un autre truc auquel je pensais, c'est que l'approche par les classid(TCustomForm) risque de poser un problème curieux si on crée une application dans laquelle on a des TForm utilisées en composants (dockées, comme des TFrames). A ce stade, je soupconne que le Parent==NULL est un test plus robuste...

    Pour la petite histoire, maintenant qu'on a parlé de ça, je repense à un bug dégoutant qu'on a eu récemment avec un composant TMS (AdvControlDropDown, un composant assez magique, soit dit en passant), et je crois que j'ai compris ce qui se passe...

    Francois

  10. #10
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2002
    Messages
    1 407
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 1 407
    Par défaut
    Salut !

    Maintenant, un cas qui serait intéressant à suivre serait celui d'une application MDI. C'est quoi l'ActiveCustomForm? Si c'est le parent MDI, je pense que tout va bien, sinon...
    Effectivement ... mais je pense que dans ce cas c'est TForm::ActiveMDIChild.

    A plus !

Discussions similaires

  1. Même couleur pour composants et sous composants
    Par formentor dans le forum AWT/Swing
    Réponses: 6
    Dernier message: 11/05/2007, 17h55
  2. Création d'un sous composant (D7)
    Par J-P-B dans le forum Composants VCL
    Réponses: 1
    Dernier message: 09/03/2006, 16h42
  3. Position d'un repertoire sous Composant TreeView ???
    Par EssaiEncore dans le forum Composants VCL
    Réponses: 2
    Dernier message: 14/11/2005, 14h33
  4. Réponses: 7
    Dernier message: 08/08/2003, 18h09
  5. exploiter un évènement d'un sous composant dans un
    Par bjl dans le forum Composants VCL
    Réponses: 2
    Dernier message: 20/12/2002, 16h44

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