Récupération de l'index de l'élément d'une collection [Résolu]

MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 5 sept. 2017 à 11:22 - Dernière réponse : MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention
- 9 sept. 2017 à 10:11
Bonjour,
C'est encore moi !
Cette fois je cherche à récupérer l'index et/ou la la clé de l'élément courant dans l'énumération d'une collection.

En effet, on peut enregistrer un élément avec une clé, et son index est créé automatiquement (au début ou à la fin selon qu'il s'agit d'une collection, d'une liste, d'une queue ou d'une pile).
on peut ensuite récupérer l'élément avec son index ou sa clé avec
collection[index]
ou
collection[clé]
.
Mais il n'existe pas (à ma connaissance) de propriété Index ou Key dans ce type d'objet.

Voici un exemple de ce que je voudrais faire (qui n'est pas forcément réaliste)
foreach(type machin in collection1)
{
    if(machin.equals(bidule)
    {
        index = machin.index;
        truc = collection2[index];
        break;
    }
}

J'ai eu beau fouiller sur le net, je n'ai pas trouvé. J'en suis réduit à faire ainsi (méthode peu fiable):
int index = 0;
foreach(type machin in collection1)
{
    if(machin.equals(bidule)
    {
        truc = collection2[index];
        break;
    }
    index++;
}

C'est pas très propre ni fiable. Quelqu'un sait comment faire autrement en récupérant l'index de l'élément ?
Afficher la suite 

Votre réponse

21 réponses

Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 5 sept. 2017 à 13:23
0
Merci
Bonjour,

Il existe la méthode IndexOf()
Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 5/09/2017 à 18:02
0
Merci
Bonjour,
Merci pour la réponse, dans le cas de mon exemple cela marche peut-être (et même sûrement), mais ce n'était pas pas ce que je cherche (j'aurais dû donner mon vrai exemple).

Cependant, cela m'a donné le bout du fil sur lequel j'ai tiré pour débobiner mon problème, que j'expose ici. Mon plus gros problème, après celui de récupérer l'index, a été la multitude de casts qu'il a fallu faire pour traiter les différents cas d'appel du gestionnaire d'évènement.

Voilà le topo et sa solution. Ça pourra éventuellement servir à d'autres.

Soit une barre de menus qui a des menus comportant plusieurs sous-menus. Ces sous-menus ont tous pour évènement Click la même procédure.
Soit encore une barre d'outils dont dont certains boutons ont des sous-boutons.
Chaque sous-bouton correspond à un sous-menu, et chaque sous-menu correspond à un sous-bouton.

Je cherche à obtenir l'index du sous-menu cliqué pour mettre à jour les boutons de la barre d'outils correspondante, et vice-versa, et déclencher l'action appropriée en fonction du type de bouton et de son index.

Par exemple :
mnuDisplayMode : ToolStripMenuItem ayant 4 ToolStripMenuItem dans sa collection DropDownItems.
tbrDisplayMode : ToolStripDropDownButton ayant 4 ToolStiipMenuItems dans sa collection DropDownItems.

Tous les sous-menus et "sous-boutons" pointent vers la même procédure d'évènement Click.

Certes, ma méthode est un peu tordue, mais je veux mutualiser l'action de plusieurs séries de menus et boutons à sous-éléments dans une seule procédure. J'ai pour principe d'essayer de produire du code réutilisable, et je pense que celui-ci en fait partie, car il n'est nulle part question du nom de l'objet déclencheur, mais seulement de son type. Il fonctionne quel que soit le nombre de menus/boutons ayant des sous-éléments, et quel que soit le nombre de sous-éléments de chacun.

Par contre, le traitement de chaque série de sous-menus/boutons est unique et dépend de l'index du menu/bouton cliqué. J'ai besoin d'un index car la procédure de traitement spécifique à une série, ici update...(), doit aussi être appelée par le code ailleurs, sous une forme numérique qui pourrait être par exemple les valeurs d'un enum, mais qui dans mon cas est une valeur mémorisée entre deux sessions pour restaurer l'environnement de fonctionnement de l'appli.

Voici le code du gestionnaire d'évènement :
// Procédure commune aux sous-menus et boutons déroulants
private void ModeAndDisplayChange(object sender, EventArgs e)
{
    if (!(sender is ToolStripMenuItem))     // Même les "sous-boutons" sont des ToolStripMenuItem
        return;

    int index; string objname, parentname;
    if (((ToolStripMenuItem)sender).OwnerItem is ToolStripMenuItem)
    {
        ToolStripMenuItem subitems = new ToolStripMenuItem();
        subitems = (ToolStripMenuItem)((ToolStripMenuItem)sender).OwnerItem;
        index = subitems.DropDownItems.IndexOf((ToolStripMenuItem)sender);
        //UpdateDisplayMode(index);
        parentname = subitems.Name; objname = ((ToolStripMenuItem)sender).Name;       // Just for debugging
    }
    else if (((ToolStripMenuItem)sender).OwnerItem is ToolStripDropDownButton)
    {
        ToolStripDropDownButton subitems = new ToolStripDropDownButton();
        subitems = (ToolStripDropDownButton)((ToolStripMenuItem)sender).OwnerItem;
        index = subitems.DropDownItems.IndexOf((ToolStripMenuItem)sender);
        //UpdateOptionMode(index);
        parentname = subitems.Name; objname = ((ToolStripMenuItem)sender).Name;        // Just for debugging
    }
    else
        return;

    switch (parentname)
    {
        case "mnuDisplayMode":
        case "tbrDisplayMode":
            UpdateDisplayMode(index); break;

        case "mnuOptionsMode":
        case "tbrOptionsMode":
            UpdateOptionMode(index); break;
    }

    Console.WriteLine("{0} = {1}[{2}]", objname, parentname, index);
}


Le résultat dans la console donne, que je clique indifféremment sur un sous-bouton (tbrXXX) ou un sous-menu (mnuXXX) :

mnuDisplayDetail = mnuDisplayMode[0]
tbrDisplayDetail = tbrDisplayMode[0]
mnuDisplayList = mnuDisplayMode[1]
tbrDisplayList = tbrDisplayMode[1]
mnuDisplaySmallIcon = mnuDisplayMode[2]
tbrDisplaySmallIcons = tbrDisplayMode[2]
mnuDisplayLargeIcons = mnuDisplayMode[3]
tbrDisplayLargeIcons = tbrDisplayMode[3]
tbrOptionsModeFile = tbrOptionsMode[1]
mnuOptionsModePath = mnuOptionsMode[0]
tbrOptionsModePath = tbrOptionsMode[0]
mnuOptionsModeFile = mnuOptionsMode[1]


Je n'ai pas trouvé le moyen de trouver une classe générique permettant d'utiliser la collection DropDownItems quel que soit son type parent (menu ou bouton) avec la même variable. Lorsque j'ai essayé, le compilateur refusait les méthodes demandées sans un cast spécifique, ce qui réduit à néant l'avantage d'une variable commune.
Peut-être en utilisant une implémentation ou une interface, mais je n'en suis pas encore là...
Commenter la réponse de MGD Software
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 5/09/2017 à 20:17
0
Merci
Je n'ai pas pu modifier le code ci-dessus, le lien "Modifier" n'étant plus présent.

J'ai amélioré et commenter le code de la procédure ci-dessus afin de le rendre plus didactique. Je le redonne donc.

// Procédure d'évènement commune aux sous-menus et boutons déroulants
private void ModeAndDisplayChange(object sender, EventArgs e)
{
    System.Diagnostics.Debug.Assert(sender is ToolStripMenuItem);

    if (!(sender is ToolStripMenuItem))     // Même les "sous-boutons" sont des ToolStripMenuItem
        return;

    int index; string objname, parentname;
    if (((ToolStripMenuItem)sender).OwnerItem is ToolStripMenuItem)                 // Le parent est un menu
    {
        ToolStripMenuItem parent = new ToolStripMenuItem();
        parent = (ToolStripMenuItem)((ToolStripMenuItem)sender).OwnerItem;          // Le menu appelant
        index = parent.DropDownItems.IndexOf((ToolStripMenuItem)sender);
        parentname = parent.Name; objname = ((ToolStripMenuItem)sender).Name;       // Just for debugging
    }

    else if (((ToolStripMenuItem)sender).OwnerItem is ToolStripDropDownButton)      // Le parent est un bouton
    {
        ToolStripDropDownButton subitems = new ToolStripDropDownButton();
        subitems = (ToolStripDropDownButton)((ToolStripMenuItem)sender).OwnerItem;  // Le bouton appelant
        index = subitems.DropDownItems.IndexOf((ToolStripMenuItem)sender);
        parentname = subitems.Name; objname = ((ToolStripMenuItem)sender).Name;     // Just for debugging
    }
    else        // Ne devrait pas arriver, mais une erreur d'affectation de handler est toujours possible
        return;

    // Dispatching des actions à entreprendre en fonction du nom de l'appelant et de l'index
    // !!! Seule partie à modifier en fonction de l'application !!!
    switch (parentname)
    {
        case "mnuDisplayMode":
        case "tbrDisplayMode":
            UpdateDisplayMode(index); break;

        case "mnuOptionsMode":
        case "tbrOptionsMode":
            UpdateOptionMode(index); break;
    }

    Console.WriteLine("{0} = {1}[{2}]", objname, parentname, index);
}


et un exemple de gestion des boutons et des menus :
private void UpdateDisplayMode(int index)
{
    if (index >= mnuDisplayMode.DropDownItems.Count
    || index >= tbrDisplayMode.DropDownItems.Count)
        return;   // Sécurité

    // Affectation de l'image du bouton cliqué au bouton déroulant
    // L'image active est ainsi visible quand le bouton est replié
    tbrDisplayMode.Image = tbrDisplayMode.DropDownItems[index].Image;

    // Gestion de la coche du menu cliqué correspondant
    foreach (ToolStripMenuItem mnu in mnuDisplayMode.DropDownItems)
        mnu.Checked = mnu.Equals(mnuDisplayMode.DropDownItems[index]);

    // Mémorisation dans un fichier .ini 
    ProfileString.SetIniParam("Options", "Display", index);  
}
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - Modifié par Whismeril le 5/09/2017 à 20:27
0
Merci
Bonsoir

j'ai répondu à la question posée.

Il ne faut pas faire ça
        ToolStripMenuItem subitems = new ToolStripMenuItem();
        subitems = (ToolStripMenuItem)((ToolStripMenuItem)sender).OwnerItem;

Quand tu fais un
trucmuche = new Toto()
, il se crée dans la RAM, une instance toute neuve de Toto, et la référence de cette instance est donnée à la variable.
Si la ligne suivante, tu écris
trucmuche = bidule
alors la référence de bidule est donnée à la variable, mais l'instance précédemment créée existe toujours (jusqu'à ce que le garbage collector se décide à vérifier s'il y a des objets abandonnés et les supprimer).

Donc tu perds du temps d'exécution à créer une instance que tu n'utilises pas, qui reste en mémoire pour un temps. Puis le garbage collector perdra aussi du temps à la supprimer.

Ceci
        ToolStripMenuItem subitems = (ToolStripMenuItem)((ToolStripMenuItem)sender).OwnerItem;
répond au besoin, sans perte de temps et d'espace mémoire.


Caster aussi prend un peu de temps, il est donc conseillé de ne le faire qu'une fois dans une variable, si on en a besoin à plusieurs reprises (et au final c'est plus lisible).

De plus pour factoriser au mieux, il faut essayer de caster en un "ancêtre commun" des objets utilisés, qui possèdent les attributs dont nous avons besoin: ToolStripDropDownItem

Voilà le code remanié (plus clair à mon avis) et moins couteux en temps d'exécution
            ToolStripMenuItem monSender = sender as ToolStripMenuItem;//caster avec as retourne null si l'objet n'est pas du type demandé
            if (monSender == null)
                return;

            ToolStripDropDownItem parent = monSender.OwnerItem as ToolStripDropDownItem;
            if (parent == null)
                return;

            string objname = monSender.Name;
            int index = parent.DropDownItems.IndexOf(monSender);
            string parentname = parent.Name;

            switch (parentname)
            {
                case "mnuDisplayMode":
                case "tbrDisplayMode":
                    UpdateDisplayMode(index);
                    break;

                case "mnuOptionsMode":
                case "tbrOptionsMode":
                    UpdateOptionMode(index);
                    break;
            }

            Console.WriteLine("{0} = {1}[{2}]", objname, parentname, index);


Quand j'étais petit, la mer Morte n'était que malade.
George Burns
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 5 sept. 2017 à 21:11
Ouais...
J'ai encore pas mal de trucs à apprendre...
Mais ça ne fait qu'un mois que je me suis lancé dans le C#.
Je pense avoir quelques excuses.

Je vais étudier ça de près.
Merci.
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 5 sept. 2017 à 21:26
Il ne s'agit pas de reproches ou de critiques au sens péjoratif, mais bien d'indications pour que tu progresses.
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention > Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 5 sept. 2017 à 21:59
C'est bien comme ça que je l'avais compris
Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 6/09/2017 à 08:20
0
Merci
Juste une question complémentaire :

À l'étude premières lignes, je crois comprendre que si l'appelant est un sous-menu, cela ne fonctionne pas.
En effet, sender est toujours un ToolStripMenuItem, mais son parent peut être soit un autre ToolStripMenuItem (appel par les menus), soit un ToolStipDropDownButton (appel par la barre d'outils)

J'ai donc modifié le second test ainsi :
            ToolStripMenuItem parent = monSender.OwnerItem as ToolStripMenuItem;
            if (parent == null)
            {
                ToolStripDropDownItem parent = monSender.OwnerItem as ToolStripDropDownItem;
                if (parent == null)
                    return;
            }

Cependant, le compilateur n'accepte pas cela à la seconde déclaration de parent pour la raison suivante : "Impossible de déclarer une variable locale ou un paramètre nommé 'parent' dans cette portée, car ce nom est utilisé dans une portée locale englobante pour définir une variable locale ou un paramètre "

Je me retrouve encore avec le problème de tenter de déclarer une même variable avec deux types différents selon la conjoncture.

C'est la raison pour laquelle je déclarais le parent après le test de type, en utilisant deux variables distinctes; et pour faire cela je ne dois pas utiliser une affectation lors du test, et utiliser seulement des casts.

je vais donc continuer avec deux blocs selon le type du parent et des variables distinctes dans chacun. C'est quand même dommage sachant que les propriétés dont j'ai besoin sont communes aux deux types d'objet.

Par contre, la déclaration des variables avec le cast "as" me plait beaucoup pour affecter ensuite la variable parent, mais m'oblige à déclarer la variable supplémentaire monSender.

Y a-t-il une grosse différence entre :
ToolStripMenuItem monSender = sender as ToolStripMenuItem;
ToolStripDropDownItem parent = monSender.OwnerItem as ToolStripDropDownItem;

et
ToolStripDropDownItem parent = (ToolStripDropDownItem)((ToolStripMenuItem)sender).OwnerItem;

ou encore :
ToolStripDropDownItem parent = ((ToolStripMenuItem)sender).OwnerItem as ToolStripDropDownItem;



Ok pour les "new", c'était effectivement une erreur et non nécessaire. Je les ai virés.
J'ai donc fait un mix entre tes remarques et mon ancien code, et ça donne ceci, qui est effectivement beaucoup plus clair même s'il faut déclarer une variable supplémentaire :
ToolStripMenuItem caller = sender as ToolStripMenuItem;
if (caller == null)
    return;

int index; string objname, parentname;
if (caller.OwnerItem is ToolStripMenuItem)                 // Le parent est un menu
{
    ToolStripMenuItem parent = caller.OwnerItem as ToolStripMenuItem;          // Le menu appelant
    index = parent.DropDownItems.IndexOf(caller);
    parentname = parent.Name; 
    objname = caller.Name;       // Just for debugging
}

else if (caller.OwnerItem is ToolStripDropDownButton)      // Le parent est un bouton
{
    ToolStripDropDownButton subitems = caller.OwnerItem as ToolStripDropDownButton;  // Le bouton appelant
    index = subitems.DropDownItems.IndexOf(caller);
    parentname = subitems.Name; 
    objname = caller.Name;     // Just for debugging
}
else        // Ne devrait pas arriver, mais une erreur d'affectation de handler est toujours possible
    return;

// Dispatching des actions à entreprendre en fonction du nom de l'appelant et de l'index
// !!! Seule partie à modifier en fonction de l'application !!!
switch (parentname)
{
    case "mnuDisplayMode":
    case "tbrDisplayMode":
        UpdateDisplayMode(index); break;

    case "mnuOptionsMode":
    case "tbrOptionsMode":
        UpdateOptionMode(index); break;
}

Console.WriteLine("{0} = {1}[{2}]", objname, parentname, index);


Plus aucun cast. Ça fonctionne. Encore des améliorations ?
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - Modifié par Whismeril le 6/09/2017 à 08:31
0
Merci
Mais non, les 2 types dérivent de ToolStripDropDownItem, donc par polymorphisme, ils sont tous les 2 de ce types.
Et de fait, c'est ce type qui introduit la propriété DropDownItems, qui est celle qui t'interresse.
Je ne sais pas si tu as lu le tuto que j'ai ecrit sur les objets, je donne un exemple, si tu veux éteindre une cafetière et une machine à laver, soit tu écris une méthode pour chacune, soit (ce qui est mieux) tu écris une méthode qui éteint les appareils électriques. Les 2 machines ont beau être foncièrement différentes, elles sont malgré tout des machines électriques et le bouton Marche/Arrêt est commun.


Le code que je t'ai posté est fonctionnel (je l'ai testé) avec les conditions que tu as donné :
Le parent est soit ToolStripMenuItem soit ToolStripDropDownButton

Quand j'étais petit, la mer Morte n'était que malade.
George Burns
Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 6/09/2017 à 10:43
0
Merci
Effectivement ça fonctionne, mais pour le coup j'ai eu une tonne de points d'interrogation. En fait, je n'avais pas vu dans ton code que la variable parent était un ToolStripDropDownItem et non un ToolStripDropDownButton (subtile nuance).

Lorsque je suis sur la page de propriétés de mes menus, leur type est ToolStripMenuItem.
Lorsque je suis sur la page de propriétés de mes boutons déroulants, leur type est ToolStripDropDownButton.

J'ai une assez bonne notion de la hiérarchie de classes, mais il ne m'était pas évident que ces deux types (menu et bouton) dérivent d'un même type appelé ToolStripDropDownItem.

Après une recherche dans l'aide, j'ai effectivement constaté les hiérarchies suivantes :

System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.ToolStripItem
System.Windows.Forms.ToolStripDropDownItem
System.Windows.Forms.ToolStripMenuItem

et

System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.ToolStripItem
System.Windows.Forms.ToolStripDropDownItem
System.Windows.Forms.ToolStripDropDownButton


Mes deux types d'objets déclencheurs ont donc bien le ToolStripDropDownItem comme "ancêtre".
J'ai un peu honte de ne pas y avoir pensé tout seul.
Encore faut-il que les propriétés et méthodes de cet ancêtre conviennent pour ce qu'on veut faire, ce qui est bien le cas ici.

Du coup je n'ai plus besoin des variables objname et parentname, j'utilise directement monSender.Name et parent.Name.

Le prochaine fois que j'aurais affaire à une variable objet à déclarer de plusieurs type différents, je referai cette recherche.

Merci pour la leçon.
Il y a 10 ans, c'est moi qui donnait des conseils sur ce même forum, section VB6.
Il faut savoir se remettre en question ;-)
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 6 sept. 2017 à 12:06
0
Merci
Regarder la page MSDN est un réflexe à prendre.
Leurs explications ne sont pas toujours claires, mais il y a systématiquement la hiérarchie d'héritage.
Comme on a affaire à 2 éléments de menu avec la même propriété DropDownItems (Name, vient d'assez haut en général), il y a toutes les chances qu'elle soit héritée d'un ancêtre commun.

J'ai un peu honte de ne pas y avoir pensé tout seul.
Non avec le changement de décors on est un peu perdu, et puis ça fonctionnait le polymorphisme en VB6? Parce que si ça devait te remonter au C++ que tu as dit avoir pratiqué il y a longtemps, on oublie tous des trucs qu'on a pas fait depuis longtemps.


Y a-t-il une grosse différence entre :
ToolStripMenuItem monSender = sender as ToolStripMenuItem;
ToolStripDropDownItem parent = monSender.OwnerItem as ToolStripDropDownItem;

et
ToolStripDropDownItem parent = (ToolStripDropDownItem)((ToolStripMenuItem)sender).OwnerItem;

ou encore :
ToolStripDropDownItem parent = ((ToolStripMenuItem)sender).OwnerItem as ToolStripDropDownItem;


La dernière ne sert à rien, tu castes 2 fois.


Pour
ToolStripDropDownItem parent = (ToolStripDropDownItem)((ToolStripMenuItem)sender).OwnerItem;
il faut être sûr et certain que OwnerItem et sender sont du type casté. Et on peut utiliser ce cast sans variable, tu le fais pour sender.

Pour
ToolStripMenuItem monSender = sender as ToolStripMenuItem;
ToolStripDropDownItem parent = monSender.OwnerItem as ToolStripDropDownItem;
là le cast est précédé d'un test, si le test est valide on caste, sinon on retourne null, ça revient à faire un if (toto is Bidule) (Bidule)toto.
C'est peut être un peu plus long à l'exécution, mais ça ne plante pas quand le type n'est pas bon.(en fait ça risque de planter après si la variable est null).
Et ensuite tu as la variable du bon type pour en faire plein de choses, et donc économiser du temps d'exécution au final car il n'y a pas 50 casts.
Et c'est quand même plus facile à lire, tu seras content, dans un an quand tu maintiendra ce code ne pas avoir à décrypter des casts en cascade.



Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 6 sept. 2017 à 12:40
0
Merci
Non, le polymorphisme n'existait pas en VB6, mais il supportait l'implémentation d'interface.
C'était beaucoup plus compliqué, car il fallait développer TOUTES les fonctions de la classe d'origine, quitte à simplement à appeler la fonction correspondante de la classe d'origine.

Autant dire que ce n'était pas très utilisé.
J'en ai fait quelquefois, mais c'était vraiment pour se faire plaisir (?).
Il était plus simple de développer une classe avec un objet interne et des méthodes qui appelaient les seules fonctions de cet objet dont on avait besoin.

Par contre, si l'objet était un contrôle, l'appli devait en posséder au moins une instance dans une fenêtre. On pouvait alors la cloner (les tableaux de contrôles existent en VB6) et l'affecter à la variable de la classe.

C'est là qu'on voit que le C# est autrement plus puissant de VB6, puisqu'on peut créer à la volée des contrôles uniquement par code.
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 6 sept. 2017 à 13:24
0
Merci
C'est là qu'on voit que le C# est autrement plus puissant de VB6
heureusement sinon quel serait l'interêt d'évoluer.

Par contre, contrairement au C++, on ne peut hériter que d'une seule mère, pour le "multi héritage" on doit passer par des interfaces, avec la même obligation de recoder les mêmes méthodes.
Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 7/09/2017 à 11:21
0
Merci
Encore une question, qui reste dans le domaine initial de cette conversation :

De la même qu'on peut obtenir l'index d'un élément dans une collection, peut-on également obtenir sa clé par exemple lors d'un passage en paramètre, ou lors d'une énumération ?

Par exemple, dans un ListView ou une List<>, je souhaiterais récupérer la clé d'un item avec laquelle on l'a créé.
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 7 sept. 2017 à 11:34
Je ne me sers jamais de listview car il n'accepte pas le binding, et je ne vois pas ce que tu appelles clé pour une List
Commenter la réponse de MGD Software
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 8 sept. 2017 à 11:40
0
Merci
Moi je me sers depuis très longtemps du listview, et pas seulement pour y afficher des données issues d'une base, mais pour tout ce qui nécessite une présentation tabulaire.

Pour les bases de données, j'ai toujours rempli le listview "à la main" à partir d'un recordset ADO, ce qui permet de gérer parfaitement les données, leur format, etc., et qui ne coûte qu'une dizaine de lignes de code.

Et sa capacité à trier les items selon une colonne donnée m'est précieuse sauf qu'en VB6 c'était automatique, et qu'avec .net il faut développer un IComparer. Mais comme j'en ai trouvé un tout fait et puissant sur le net, c'est parfait.

En plus, le listview du framework possède une propriété "databinding" qui il me semble permet de le lier à une source de données. Mais je continuerai ma méthode de remplissage. Je n'aime pas trop les trucs automatiques qui demandent une usine à gaz dès qu'on veut sortir du moule.

Je viens de découvrir que les List<> de C# ne ressemblent pas du tout, hélas, aux collections de VB6, pourtant si commodes pour mettre des objets en vrac et les retrouver avec leur clé.
Aucune des collections que j'ai trouvées dans C# n'ont pas la notion de clé.

À priori, même les tableaux de C# n'ont pas la notion de clé, comme en PHP ou en Javascript. Ou alors, je n'ai pas bien cherché ?

J'aimerai pouvoir faire, comme en PHP par exemple :
toto = array("machin'=>2, "truc"=>8, "bidule"=4);
toto["xyz"] = 12;
y = toto["truc"];

J'ai pourtant cru voir que c'était possible pour les recordset car dans mon bouquin j'ai trouvé un exemple avec rowN["champ"].
Mais ni dans la MSDN ni sur le net je n'ai trouvé comment les initialiser ni comment s'en servir. Pourtant cela correspondrait à mon besoin de recherche une valeur par sa clé (non numérique).
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 8 sept. 2017 à 13:33
0
Merci
C'est bien ce que je pensais, et effectivement ça n'existe pas avec les listes, pourquoi enregistrer une information supplémentaire alors qu'il y a tous les outils pour extraire une instance, par exemple:
Toto instance = maListe.SingleOrDefaut(x => x.Name == "le nom")


Sinon tu peux utiliser la collection Dictionnary.

Effectivement, la listeview possède la propriété databinding, car elle est héritée de Control, mais ça ne marche pas ou pas bien.

Il faut savoir que C# est optimiser pour le MVC, et donc remplir une contrôle à la main va à l'encontre de ça.
Par exemple, un textbox ou un cellule de datagridview qui contient un nombre, une date, un booléen, bref tout sauf du texte, si tu le fais à la main, il te faudra convertir dans le sens contrôle variable et dans le sens inverse. Le binding le fait pour toi, de plus, le contrôle sera actualisé sans ligne de code supplémentaire dès que le variable est mise à jour et inversèrent.

Tu dis qu'il faut une usine à gaz pour sortir du moule, en winform c'est parfois le cas, notamment avec la listeview.
Par contre en WPF, c'est assez simple de personnaliser absolument tout ce qui est imaginable, d'ailleurs la listeview n'existe même pas.
Commenter la réponse de Whismeril
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 8 sept. 2017 à 18:43
0
Merci
Je ne maitrise pas encore la technique du => qui est très spécifique du C#.

Dans l'exemple, cela sous-entend que x est un objet qui a plusieurs propriétés, dont name pour cet exemple. Ce qui veut dire que si je veux stocker des objets simples dans ma liste, des string par exemple, je suis obligé de créer une classe spécifique avec par exemple les propriétés name et value. C'est super lourd et ça ne me convient pas.

Le type dictionnary convient très bien pour ce que je veux faire, et je l'ai déjà utilise en VB6. Mais c'est un objet que je crée et je gère entièrement, ce n'était le but de ma question initiale, qui concernait les collections intrinsèques du framework.

Je suppose que dans une liste de sous-menus, on peut faire une itération sur les sous-menus et récupérer par exemple celui qui est coché par sa propriété Name.

string name;
foreach (ToolStripMenuItem item in caller.DropDownItems)
    if (item.Checked)
    {
        name = item.Name;
    }

On peut ensuite utiliser le menu avec caller.DropDownItems[name] (enfin, j'espère, je n'ai pas encore testé). if faut bien sûr gérer le cas du name == "";

J'aurais aimé faire la même chose pour toutes les collections du framework, y compris les ListViewItems.

La collection ListView.Item (type : ListViewItemCollection) a bien pour méthode :
Add(String, String, String) 
qui "crée un élément avec la clé, le texte et l'image spécifiés et l'ajoute à la collection".
Il est donc possible de désigner un item par :
ListViewItem item = monlistview.items["maclé"]

Ce que je cherche à faire, c'est récupérer cette clé dans un traitement comme celui du menu ci-dessus. C'était possible avec l'objet DCOM du même nom. C'est étonnant qu'on ne puisse pas faire la même chose avec celui du Framework.
Commenter la réponse de MGD Software
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - Modifié par MGD Software le 8/09/2017 à 19:32
0
Merci
J'ai trouvé !!

La propriété Key que je cherchais (propriété sous VB6) s'appelle désormais Name.

Je suppose que c'est pour une raison d'homogénéité avec les autres types de contrôle, qui ont tous une propriété Name. Pourtant, les classes dont ListViewItem dérivent (Object -> System.Web.UI.Control) n'ont pas de propriété Name. En tous cas, je n'en ai pas trouvé dans la MSDN. Ce n'est donc pas un héritage. Cependant, elle n'apparaît pas non plus parmi les propriétés affichées dans la MSDN. Alors ...? Peut-être que cela hérite de Object, car il serait étonnant qu'un objet n'ait pas de nom, même non typé.

Voilà un exemple de ce que je cherchais à faire :
public void test()
{
    lvwFiles.Items.Clear();
    lvwFiles.Items.Add("K1", "Ligne 1", null);
    lvwFiles.Items.Add("K2", "Ligne 2", null);
    lvwFiles.Items.Add("K3", "Ligne 3", null);
    lvwFiles.Items.Add("K4", "Ligne 4", null);

    // .....

    foreach (ListViewItem item in lvwFiles.Items)
        Console.WriteLine(item.Name + " = " + item.Text);
}


Ce qui donne :
K1 = Ligne 1
K2 = Ligne 2
K3 = Ligne 3
K4 = Ligne 4


Tout ça pour ça...

Mais cette discussion n'aura pas été inutile pour beaucoup d'autres raisons, notamment la gestion menus/boutons.
Commenter la réponse de MGD Software
Whismeril 12119 Messages postés mardi 11 mars 2003Date d'inscriptionContributeurStatut 21 octobre 2018 Dernière intervention - 8 sept. 2017 à 19:48
0
Merci
Je ne maitrise pas encore la technique du => qui est très spécifique du C#.
C'est ce qu'on appelle une expression lamba, et ça existe dans plusieurs langages, Java par exemple.
En gros x (ou ce que tu veux, c'est une variable, tu peux aussi bien l'appeler leTrucQueJeCherche), représente une instance de ce contient la liste, => veut dire avec ce x, je cherche la condition décrite ensuite, dans le cas d'une recherche.
Par exemple la moyenne d'âge d'une liste de personnes
double moyenne = mesPersonnes.Average(p => p.Age);

On peut aussi utiliser des expressions lambda qui retourne un résultat (j'ai pas d'exemples qui me viennent comme ça).
Voir ce tuto
http://blogs.developpeur.org/tom/archive/2006/06/18/21672.aspx


Ce qui veut dire que si je veux stocker des objets simples dans ma liste, des string par exemple, je suis obligé de créer une classe spécifique avec par exemple les propriétés name et value.
là ça dépend de ce que tu veux faire, si par exemple c'est retrouver patate en cherchant "patate" (ça m'étonnerait, mais je te le mets quand même)
            List<string> maListe = { "Tomane", "Patate", "Fraise" };
            string mot = maListe.FirstOrDefault(s => s.ToLower() == "patate");

Par contre, si c'est pour associer rouge et fraise, orange et mandarine, verte et pomme, alors le dictionnaire ou la liste de KeyPairValue sont les 2 solutions les plus simples.



string name;
foreach (ToolStripMenuItem item in caller.DropDownItems)
    if (item.Checked)
    {
        name = item.Name;
    }

Oui, ou
string name = caller.DropnItems.First(m => m.Checked).Name;


caller.DropDownItems[name]
je n'ai pas testé non plus mais je n'y crois pas.
Et en plus ça "ne sert à rien"

LaClasseQuiVaBien monMenu = caller.DropnItems.First(m => m.Checked);
monMenu.CeQueTuVeux();


Le principe de base de C# est de travailler avec des objets, pas avec leur index ou nom de code dans une collection.

Et si tu te dis que passer un objet d'une méthode à l'autre ou d'une classe à l'autre utilise plus de RAM que de passer son index, en fait c'est l'inverse.
En effet, les types de base (int, bool, double, etc...) les énumérations et les structures sont des types "valeur", à chaque passage une nouvelle instance est crée et la valeur est copiée (on multiplie donc valeur et référence). Tous les autres types, sont des classes et les classes sont des types "référence", c'est la référence de l'objet qui est transférée (un peu comme le pointeur de C++, mais pointeur est un gros mot en c#).

Et puis la clé en string a quand même un gros défaut, tu cherches "Evinçons", "évinçons", "Évinçons", "EVINCONS", "EVINÇONS", etc...


Il y a quelques exceptions à ce principe.
Pour la ListView, j'imagine qu'avant WPF, les dev n'avait pas trouvé mieux pur afficher "facilement" une image qui représente un objet.
Pour le dataSet (ou recordset), c'est pareil je suppose, avant l'arrivée de Linq, y'avait pas mieux.

Ce que je cherche à faire, c'est récupérer cette clé dans un traitement comme celui du menu ci-dessus. C'était possible avec l'objet DCOM du même nom. C'est étonnant qu'on ne puisse pas faire la même chose avec celui du Framework.

Si tu insistes, tu peux toujours stocker ta clé dans la propriété Tag.
LaClasseQuiVaBien monMenu = caller.DropnItems.First(m => m.Checked);
string cle = (string)monMenu.Tag;
MGD Software 96 Messages postés vendredi 1 septembre 2006Date d'inscription 22 septembre 2018 Dernière intervention - 9 sept. 2017 à 10:11
>> Si tu insistes, tu peux toujours stocker ta clé dans la propriété Tag.

C'est justement ce que j'ai fait dans ma première appli en C# publiée, dont l'interface est essentiellement un ListView.
http://mgd.software.free.fr/downloads/Freewares/Media_Shuffle
(réécriture en C# d'un vieux programme en VB6)

Mais le tag a l'inconvénient de ne pas être un index, donc pas moyen de réadresser l'item avec [tag]. Mais maintenant que j'ai Name, ça va aller mieux.

Et pour parodier Lamartine :
Objects inanimés, avez-vous donc un Name qui s'attache à votre Name et vous force à l'aimer ?

Pour le reste, je vais relire ton post à tête reposée, car il y a beaucoup de choses dedans.
Merci
Commenter la réponse de Whismeril

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.