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# Discussion :

partager une list<string> entre 2 thread


Sujet :

C#

  1. #1
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut partager une list<string> entre 2 thread
    Bonjour

    Je cherche a écouter un port UDP sur lequel je dois recevoir une trame périodiquement.
    Là je le fais en remplissant une list<String> ainsi
    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
            private  void UDPListener(int portEcoute)
            {
                Task.Run(async () =>
                {
                    IPEndPoint myIPendPoint = new IPEndPoint(IPAddress.Any, portEcoute);
     
                    using (var udpClient = new UdpClient(myIPendPoint))
                    {
                        string loggingEvent = "";
     
                        while (true)
                        {
                            var receivedResults = await udpClient.ReceiveAsync();
     
                            loggingEvent += Encoding.ASCII.GetString(receivedResults.Buffer);
     
                            mylist.Add(loggingEvent);
     
                            loggingEvent = "";
                        }
                    }
                });
            }
    le but étant de pouvoir afficher ce que j'ai reçu sur le thread de la form
    avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
                textBox1.Clear();
     
                foreach (var item in mylist)
                {
                    textBox1.AppendText(item + " \r\n");
                }
    que j'ai pour le moment mis sur un timer (ça clignote, c'est moche mais c'est pour apprendre...)

    tout ceci fonction jusqu'à un beau plantage System.InvalidOperationException
    La collection a été modifiée ; l'opération d'énumération peut ne pas s'exécuter

    forcément j'ai un thread qui rempli la list<string> et un autre qui cherche à faire une énumération.

    En c avec les OS temps réels je connais les mutex pour gérer ça mais là je n'arrive pas à trouver le nom de ce que j'ai besoin de mettre en place pour éviter le conflit.

    Merci par avance pour votre aide
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  2. #2
    Expert confirmé
    Avatar de wallace1
    Homme Profil pro
    Administrateur systèmes
    Inscrit en
    Octobre 2008
    Messages
    1 966
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Octobre 2008
    Messages : 1 966
    Points : 4 005
    Points
    4 005
    Billets dans le blog
    7
    Par défaut
    bonjour,

    Si tu souhaites modifier ta liste et etre notifié d'un ajout ou une supression à un instant T alors peut etre devrais tu utiliser une ObservableCollection.
    Regardes ce sujet, en espérant qu il puisse t inspirer :
    http://www.developpez.net/forums/d15...c/#post8671424

    a+

  3. #3
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Outch

    Autant C# est redoutablement simple et puissant pour pleins de trucs, autant ça devient une usine à gaz pour gérer les échanges entre thread, entre forme et compagnie !
    allé je m'accroche, demain matin je saurai j'espère mettre en œuvre ces 553425233 déclarations pour avoir un évènement !

    et dire qu'un mutex ça se gère en 1 ligne...

    Merci
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  4. #4
    Membre chevronné
    Avatar de Sehnsucht
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Octobre 2008
    Messages
    847
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Lot et Garonne (Aquitaine)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Octobre 2008
    Messages : 847
    Points : 2 209
    Points
    2 209
    Par défaut
    Remettons les choses à leur place : l'erreur que tu obtiens n'est pas (directement) liée aux thread ; elle te signifie juste que tu as modifié la collection sur laquelle tu itérais (que ce soit par un [autre] thread ou pas n'entre pas en ligne de compte). On aurait exactement la même erreur ici :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    static void Main (){
        var sample = new List<int> { 42 };
     
     
        foreach (var item in sample)
            sample.Add (666);
    }
    Donc déjà tu peux remplacer ton foreach par un for classique et voir si ça plante toujours (probablement pas) et si le comportement est bien celui escompté.

    Quant au mutex ; il y a pléthore d'option dans l'espace de nom System.Threading (les classes Mutex, Monitor, Semaphore ou ReaderWriterLock notamment) sans parler du mot clef lock
    Nous sommes tous plus ou moins geek : ce qui est inutile nous est parfaitement indispensable ( © Celira )
    À quelle heure dormez-vous ?
    Censément, quelqu'un de sensé est censé s'exprimer sensément.

  5. #5
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Merci pour la remise en place

    comme je suis en plein apprentissage je vais quand même me battre pour tenter de comprendre ces evenements. Je les manipules depuis des années via les propriétés des composants mais via le code ou mes propres classes j'ai jamais tenté plus qu'un orteil dans cette zone trouble.
    C'est quand m^me la grande classe de faire un truc quand il y a besoin plutôt que toutes les demi-secondes..
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  6. #6
    Expert éminent sénior Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 154
    Points : 25 072
    Points
    25 072
    Par défaut
    Citation Envoyé par petitours Voir le message
    Outch

    Autant C# est redoutablement simple et puissant pour pleins de trucs, autant ça devient une usine à gaz pour gérer les échanges entre thread, entre forme et compagnie !
    allé je m'accroche, demain matin je saurai j'espère mettre en œuvre ces 553425233 déclarations pour avoir un évènement !

    et dire qu'un mutex ça se gère en 1 ligne...

    Merci
    il n'y a rien de compliqué, c'est parce que tu ne sais pas que tu penses que c'est compliqué
    avec lock ca fait une ligne de code pour que chacun ne puisse pas faire leur truc en même temps
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  7. #7
    Membre régulier
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2016
    Messages
    64
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2016
    Messages : 64
    Points : 91
    Points
    91
    Par défaut
    Salut,

    comme dit plus haut ton erreur n'est pas (directement) liée au multithreading.
    Pour gérer les collections en multithread, tu as toutes les collections de l'espace de nom System.Collections.Concurrent.
    Pour ton problème précis, c'est parce que tu ne peux pas modifier un IEnumerable en même temps que tu utilises son énumérateur (qui est créé implicitement dans une boucle foreach).
    Plus haut quelqu'un a parlé d'ObservableCollection ; c'est bien pour notifier automagiquement les modifs d'une collection et les répercuter ailleurs, (ça implémente INotifyCollectionChanged, une sorte de pattern Observer pour les collections), c'est utilisé nativement par les contrôles type List etc. dans WPF par exemple). Mais dans ton cas ça ne marcherait pas, parce que l'ajout au contrôle visuel serait lancé par un thread différent de celui qui l'a créé et ça planterait.
    Pour résoudre très rapidement ton problème, tu peux par ex. utiliser lock. Tu lui spécifies une référence d'objet (en général un objet qui ne qu'à ça), et tant qu'un thread est à l'intérieur de l'instruction définie par le lock, les autres threads sont bloqués et doivent attendre pour eux-mêmes pouvoir mettre un lock dessus :
    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
     
    ....
    ....
    private readonly object synclock = new object();
    ....
    ...
     
    List<string> listCopy = null;
    lock (this.synclock)
    {
    listCopy = mylist.ToList();
    }
     foreach (var item in listCopy)
                {
                    textBox1.AppendText(item + " \r\n");
                }
    et

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    lock (this.synclock)
    {
    mylist.Add(loggingEvent);
    }

  8. #8
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    merci
    Je me rabats sur lock faute de réussir à gérer les évènements.
    mais je vais y revenir, là je lis ma liste toutes les 2secondesqu'il y ait eu changement ou pas, ce serait topissime de déclencher un événement quand il y a modification ; plus te taches inutiles et plus réactif.
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  9. #9
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Bonjour

    Je reviens à la charge avec ma question qui a un peu évoluée
    Là je tente d'afficher dans un datagridview des infos que je reçois par UDP grâce à une tache dans un autre thread et un datatable
    voici la classe, qui rempli et modifie bien la DataTable quand les trames UDP arrivent
    J'ai abandonné la List au profit du datatable parce que je vais devoir faire des requêtes sur les données et le DataTable s'y prête mieux, comme le reste de l'appli est basé que sur des datatables.
    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
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net.Sockets;
    using System.Net ;
    using System.Data;
     
    namespace Test
     
        public class UDPidentite
        {
     
            /// <summary>
            /// Constructeur avec creation de la DataTable
            /// </summary>  
            public UDPidentite()
            {
                // init de la table
                TableUDP = new DataTable();
                TableUDP.Columns.Add("Date dernière vue", typeof(DateTime));
                TableUDP.Columns.Add("IP", typeof(string));
                TableUDP.Columns.Add("Nom", typeof(string));
                TableUDP.Columns.Add("Adresse MAC", typeof(string));
                TableUDP.Columns.Add("Status", typeof(string));
            }
     
            /// <summary>
            /// DataTable contenant les identifications recues par UDP
            /// </summary>                 
            public DataTable TableUDP { get; set; }
     
     
            public void UDPListener(int portEcoute)
            {
                Task.Run(async () =>
                {
                    IPEndPoint myIPendPoint = new IPEndPoint(IPAddress.Any, portEcoute);
     
                    using (var udpClient = new UdpClient(myIPendPoint))
                    {
                        string loggingEvent = "";
     
                        while (true)
                        {
                            var receivedResults = await udpClient.ReceiveAsync();
     
                            loggingEvent += Encoding.ASCII.GetString(receivedResults.Buffer);
     
                            ReceptPeriphUDP(loggingEvent);
     
                            loggingEvent = "";
                        }
                    }
                });
            }
     
            /// <summary>
            /// Ajoute la ligne spécifiée à la table 
            /// </summary>
            private void ReceptPeriphUDP(string Trame)
            {
                String[] substrings = Trame.Split('#');
     
                if (substrings[0] == "entete")
                {
                    Boolean Exite = false;
     
                    // on regarde chaque ligne de la datatable pour voir si on vient de recevoir une IP qui existe déjà
                    foreach (DataRow myrow in TableUDP.Rows)
                    {
                        if (myrow["IP"].ToString() == substrings[2]) // si l'ip existe déjà 
                        {
                            myrow.BeginEdit();
                            myrow["IP"] = substrings[2];
                            myrow["Nom"] = substrings[1];
                            myrow["Adresse MAC"] = substrings[3];
                            myrow["Status"] = substrings[4];
                            myrow["Date dernière vue"] = DateTime.Now;
                            myrow.EndEdit();
     
                            Exite = true;                        
                        }
                       //faire ici l'autre test sur chaque élément
                    }
     
                    if (Exite == false) // si on a pas trouvé l'élément on le crée
                    {
                        DataRow dr = TableUDP.NewRow();
                        dr["IP"] = substrings[2];
                        dr["Nom"] = substrings[1];
                        dr["Adresse MAC"] = substrings[3];
                        dr["Status"] = substrings[4];
                        dr["Date dernière vue"] = DateTime.Now;
                       // dr["Date dernière vue"] = DateTime.Now.ToString("dd/mm/yy HH:mm:ss");
     
                        TableUDP.Rows.Add(dr);
                    }
     
     
     
                }
     
     
     
            }
     
     
        }
     
     
    }
    la DataTable est mise en Datasource d'un datagridView et...ça ne se met pas à jour ce qui semble normal.
    En mettant un bouton qui force le refresh du datagridview bindé à la datatable je vois bien les modifications apparaitre (ce qui me fait dire que cette classe fait son job)

    De ce que j'ai compris le datagridview ne se met pas à jour pour 2 raisons :
    -La modification de la Datatable se fait dans un autre thread
    -Une modification de la datatable n'impacte pas un datagridview bindé dessus (ça ne mettrait à jour que dans le sens datagridview vers datatable...why ?)

    J'ai tenté d'ajouter à ma classe un évènement qui lèverait à chaque modification de la datatable pour pouvoir faire un refresh du datagridview mais cet évènement déclanche une fois et aprés la tache semble ne plus fonctionner ! plus de trame UDP recue.

    La question est quelle stratégie doit on utiliser dans ce cas ?
    1)-Gérer un évènement (mon échec sur l'évènement serait juste un mauvaise gestion de l'évènement de ma part...)
    2)-Que la tache puisse mettre à jour le datagridview (ça je sais que c'est maaaal, on sépare la couche métier de l'interface)
    3)-Que le task Run retourne un résultat ? mais là j'ai cherché des heures et (si c'est possible) je n'ai pas trouvé comment une tache peut retourner un résultat tout en continuant à fonctionner (ici j'ai un while(true) qui doit retourner un résultat à chaque tour)
    4)-Autre chose ?

    Merci par avance
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  10. #10
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Voici la même classe avec la tentative d'évènement qui déclenche bien la première fois qu'une trame arrive maisqui fait que la task arrète de tourner ensuite (on ne revoit plus rien).
    en gros soit je me craque complet sur l'utilisation des évènements, soit c'est pas compatible avec les task.

    la classe avec génération d'event
    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
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net.Sockets;
    using System.Net ;
    using System.Data;
     
    namespace test
    {
     
     
     
        /// <summary>
    	/// A la responsabilité de contenir le text de l'évenement et de le rendre accessible
    	/// </summary>
    	public class TableChangedEventArgs : EventArgs
        {
            private string myEventText = null;
     
            public TableChangedEventArgs(string theEventText)
            {
                if (theEventText == null) throw new NullReferenceException();
                myEventText = theEventText;
            }
     
            public string EventText
            {
                get { return this.myEventText; }
            }
        }
     
        public class UDPidentite
        {
            public delegate void ChangedEventArgsHandler(object sender, TableChangedEventArgs e);
     
            public event ChangedEventArgsHandler OnTableUDPChanged;
            /// <summary>
            /// Constructeur avec creation de la DataTable
            /// </summary>  
            public UDPidentite()
            {
                // init de la table
                TableUDP = new DataTable();
                TableUDP.Columns.Add("Date dernière vue", typeof(DateTime));
                TableUDP.Columns.Add("IP", typeof(string));
                TableUDP.Columns.Add("Nom", typeof(string));
                TableUDP.Columns.Add("Adresse MAC", typeof(string));
                TableUDP.Columns.Add("Status", typeof(string));
            }
     
     
     
     
     
            /// <summary>
            /// DataTable contenant les identifications recues par UDP
            /// </summary>                 
            public DataTable TableUDP { get; set; }
     
     
            public void UDPListener(int portEcoute)
            {
                Task.Run(async () =>
                {
                    IPEndPoint myIPendPoint = new IPEndPoint(IPAddress.Any, portEcoute);
     
                    using (var udpClient = new UdpClient(myIPendPoint))
                    {
                        string loggingEvent = "";
     
                        while (true)
                        {
                            var receivedResults = await udpClient.ReceiveAsync();
     
                            loggingEvent += Encoding.ASCII.GetString(receivedResults.Buffer); //on passe ici une fois à la première trame recue puis plus jamais
     
                            ReceptPeriphUDP(loggingEvent);
     
                            TableChangedEventArgs e = new TableChangedEventArgs("Fait");
                            if (e != null) OnTableUDPChanged(this, e);
     
                            loggingEvent = "";
                        }
                    }
                });
            }
     
            /// <summary>
            /// Ajoute la ligne spécifiée à la table 
            /// </summary>
            private void ReceptPeriphUDP(string Trame)
            {
                String[] substrings = Trame.Split('#');
     
                if (substrings[0] == "entete")
                {
                    Boolean Exite = false;
     
                    // on regarde chaque ligne de la datatable pour voir si on vient de recevoir une IP qui existe déjà
                    foreach (DataRow myrow in TableUDP.Rows)
                    {
                        if (myrow["IP"].ToString() == substrings[2]) // si l'ip existe déjà 
                        {
                            myrow.BeginEdit();
                            myrow["IP"] = substrings[2];
                            myrow["Nom"] = substrings[1];
                            myrow["Adresse MAC"] = substrings[3];
                            myrow["Status"] = substrings[4];
                            myrow["Date dernière vue"] = DateTime.Now;
                            myrow.EndEdit();
     
                            Exite = true;
                            break;
                        }
     
     
                    }
     
                    if (Exite == false) // si on a pas trouvé l'élément on le crée
                    {
                        DataRow dr = TableUDP.NewRow();
                        dr["IP"] = substrings[2];
                        dr["Nom"] = substrings[1];
                        dr["Adresse MAC"] = substrings[3];
                        dr["Status"] = substrings[4];
                        dr["Date dernière vue"] = DateTime.Now;
                       // dr["Date dernière vue"] = DateTime.Now.ToString("dd/mm/yy HH:mm:ss");
     
                        TableUDP.Rows.Add(dr);
                    }
     
     
     
                }
     
     
     
            }
     
     
        }
     
     
    }
    que j'utilise ainsi
    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
     
            UDPidentite myUDP = new UDPidentite();
     
    private void Form1_Load(object sender, EventArgs e)
            {
                myUDP.OnTableUDPChanged += new UDPidentite.ChangedEventArgsHandler(MyUDP_OnTableUDPChanged);  
                dataGridViewUDP.DataSource = myUDP.TableUDP;
                myUDP.UDPListener(Properties.Settings.Default.PortUDP);
     
                dataGridViewUDP.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
                dataGridViewUDP.ReadOnly = true; //pas de modi possible directement dans le datagridview
                dataGridViewUDP.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // selection d'une ligne complète
                dataGridViewUDP.MultiSelect = false;
                dataGridViewUDP.AllowUserToAddRows = false;
                dataGridViewUDP.AllowUserToDeleteRows = false;
     
                dataGridViewUDP.Columns["Date dernière vue"].DefaultCellStyle.Format = "hh:mm:ss";
            }
     
            private void MyUDP_OnTableUDPChanged(object sender, TableChangedEventArgs e)
            {
                dataGridViewUDP.Refresh(); //on passe ici une fois à la première trame reçue puis plus jamais
            }
    Merci par avance
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  11. #11
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Bon j'ai essayé 50 trucs et je sèche pour trouver la solution élégante
    Je me suis résolu à passer le datagridview en paramètre à la classe "métier"...

    ça donne ça
    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
    using System;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net.Sockets;
    using System.Net ;
    using System.Data;
    using System.IO;
    using System.Windows.Forms;
     
    namespace test
    {
     
     
        public class UDPidentite
        {
            private static UDPidentite _SingletonInstance = new UDPidentite();
     
            //le datagridViewPassé en paramètres à la construction
            private DataGridView m_Dgv { get; set; }
     
            /// <summary>
            /// Instance unique de la classe UDPidentité pour que tout le monde accède au même jeu de donnée
            /// </summary>
            public static UDPidentite SingletonInstance
            {
                get { return _SingletonInstance; }
            }
     
            public UDPidentite()
            {
            }
     
            /// <summary>
            /// Constructeur avec creation de la DataTable
            /// </summary>  
            public void UDPLancerEcoute(int portEcoute, DataGridView Dgv)
            {
                m_Dgv = Dgv; // Récupération du datagridView à rafraichir
     
                // init de la table
                TableUDP = new DataTable();
                TableUDP.Columns.Add("Date dernière vue", typeof(DateTime));
                TableUDP.Columns.Add("IP", typeof(string));
                TableUDP.Columns.Add("Nom", typeof(string));
                TableUDP.Columns.Add("Adresse MAC", typeof(string));
                TableUDP.Columns.Add("Status", typeof(string));
     
                // Création de la connexion UDP
                UdpClient myUdpClient = new UdpClient(portEcoute);
     
                myUdpClient.BeginReceive(new AsyncCallback(myOnReveive), myUdpClient);
            }
     
     
            private void myOnReveive(IAsyncResult result)
            {
                //on recupère le client utilisé à l'écoute
                UdpClient myUdpClient = result.AsyncState as UdpClient;
     
                //Endpoint pour savoir qui a envoyé le message
                IPEndPoint source = new IPEndPoint(0, 0);
     
                // recupération du message et de la source
                byte[] message = myUdpClient.EndReceive(result, ref source);
     
                string loggingEvent = Encoding.UTF8.GetString(message);
     
                ReceptPeriphUDP(loggingEvent);
     
                // schedule de la prochaine opération d'écoute
                myUdpClient.BeginReceive(new AsyncCallback(myOnReveive), myUdpClient);
            }
     
     
            /// <summary>
            /// DataTable contenant les identifications recues par UDP
            /// </summary>                  
            public DataTable TableUDP { get; set; }
     
     
            /// <summary>
            /// Ajoute la ligne spécifiée à la table 
            /// </summary>
            private void ReceptPeriphUDP(string Trame)
            {
                String[] substrings = Trame.Split('#');
     
                if (substrings[0] == "IBmaster")
                {
                    Boolean Exite = false;
     
                    // on regarde chaque ligne de la datatable pour voir si on vient de recevoir une IP qui existe déjà
                   // foreach (DataRow myrow in TableUDP.Rows)
                    for (int i = 0 ; i < TableUDP.Rows.Count ; i++)
                    {
                        if (TableUDP.Rows[i]["IP"].ToString() == substrings[2]) // si l'ip existe déjà 
                        {
                            TableUDP.Rows[i].BeginEdit();
                            TableUDP.Rows[i]["IP"] = substrings[2];
                            TableUDP.Rows[i]["Nom"] = substrings[1];
                            TableUDP.Rows[i]["Adresse MAC"] = substrings[3];
                            TableUDP.Rows[i]["Status"] = substrings[4];
                            TableUDP.Rows[i]["Date dernière vue"] = DateTime.Now;
                            TableUDP.Rows[i].EndEdit();
     
                            Exite = true;
                        }
                    }
     
                    if (Exite == false) // si on a pas trouvé l'élément on le crée
                    {
                        DataRow dr = TableUDP.NewRow();
                        dr["IP"] = substrings[2];
                        dr["Nom"] = substrings[1];
                        dr["Adresse MAC"] = substrings[3];
                        dr["Status"] = substrings[4];
                        dr["Date dernière vue"] = DateTime.Now;
                       // dr["Date dernière vue"] = DateTime.Now.ToString("dd/mm/yy HH:mm:ss");
     
                        TableUDP.Rows.Add(dr);
     
                    }
     
                    m_Dgv.Invoke((Action)(() =>
                    {
                        m_Dgv.SuspendLayout();
                        m_Dgv.Refresh();
                        m_Dgv.ResumeLayout();
                    }));
     
                }
     
     
     
            }
     
     
        }
     
     
    }
    du coup la mise à jour du datagridView se fait avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
                    m_Dgv.Invoke((Action)(() =>
                    {
                        m_Dgv.SuspendLayout();
                        m_Dgv.Refresh();
                        m_Dgv.ResumeLayout();
                    }));
    Surement pas la bonne technique puisque j'ai mon datagridview qui scintille et a un comportement étrange à la sélection comme s'il était "occupé" par moment, mais je ne sais plus où chercher...
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  12. #12
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 760
    Points : 10 541
    Points
    10 541
    Billets dans le blog
    21
    Par défaut
    Pourtant, ton premier essai était presque bon !

    C'est juste qu'avec Task.Run, ton code est exécuté sur un thread appartenant à un pool de thread, et donc différent du thread gérant l'interface utilisateur. Si tu veux appeler une méthode d'un Control (ici le Refresh du DataGridView), tu dois vérifier la valeur de la propriété "InvodeRequired". Si elle est à true, alors tu dois utiliser la méthode Invoke sur ton DataGridView pour appeler la méthode Refresh. Il faut que tu aies quelque chose comme cela dans ta méthode MyUDP_OnTableUDPChanged

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    if (dataGridViewUDP.InvokeRequired)
    {
       dataGridViewUDP.Invoke(new Action(() => dataGridViewUDP.Refresh()));
    }
    else
    {
       dataGridViewUDP.Refresh(); 
    }
    L'aspect fourbe, c'est qu'en temps normal du devrait avoir une exception de remontée. Lorsque tu lances une tache via Task.Run et si une exception se produit, la remontée de l'exception se fait au moment où tu interragies avec la tache (par exemple, en l'attendant ou en récupérant sa valeur de retour). Ceci pour permettre de traiter l'exception via un bloc try/catch. Ici, comme tu ne fais rien de tout cela, l'exception est... perdue !
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  13. #13
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    bonjour et merci !

    C'est ma tentative de gestion des events qui est presque bonne en #10 ? juste avec cette bricole en plus ça ne doit plus bloquer ma task (pas réussi à comprendre ce qui bloque) ?

    Je ne comprends pas la dernière remarque sur les exceptions. Pour le moment je ne gère pas les try catch par simplicité, je vais y venir et du coup si exception elle m’expose à la figure là, non ?

    Avec le InvokeRequired je peux espérer ne plus avoir mon datagridview qui scintille ? par ce que c'est vraiment très vilain tel que je l'ai là avec m_Dgv.Refresh();.

    Merci
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  14. #14
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 760
    Points : 10 541
    Points
    10 541
    Billets dans le blog
    21
    Par défaut
    C'est ma tentative de gestion des events qui est presque bonne en #10 ? juste avec cette bricole en plus ça ne doit plus bloquer ma task (pas réussi à comprendre ce qui bloque) ?
    Oui, c'est ça ! J'aurais dû mieux le préciser

    Je ne comprends pas la dernière remarque sur les exceptions. Pour le moment je ne gère pas les try catch par simplicité, je vais y venir et du coup si exception elle m’expose à la figure là, non ?
    Ce n'est pas grave. C'était juste pour essayer de t'expliquer pourquoi tu n'avais pas eu d'exception. En d'autres termes, elle est bien déclenchée au niveau de la tâche (c'est pour cela que ton code ne s'exécute qu'une fois, car la tâche est interrrompue suite à une exception non gérée), mais n'est pas remontée à la tâche parente (c'est pour cela que tu n'as rien vu).

    Avec le InvokeRequired je peux espérer ne plus avoir mon datagridview qui scintille ? par ce que c'est vraiment très vilain tel que je l'ai là avec m_Dgv.Refresh();.
    J'ai peur que le scintillement soit dû au Refresh lui même. Il faudrait changer la source de ta GridView. Peut-être en utilisant une BindingSource ? (Une BindingSource peut notifier le Control auquel il est associé que des éléments ont été modifiés).
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  15. #15
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Citation Envoyé par dorinf Voir le message
    J'ai peur que le scintillement soit dû au Refresh lui même.
    flute
    Citation Envoyé par dorinf Voir le message
    Il faudrait changer la source de ta GridView. Peut-être en utilisant une BindingSource ? (Une BindingSource peut notifier le Control auquel il est associé que des éléments ont été modifiés).
    Pour le moment le datagridview est bindé à une Datatable. L'idée serait de mettre la datatable en datasource d'un bindingsource qui serait datasource du datagridview ? avec ça je peux espérer ne plus avoir le scintillement ?


    Merci infiniment, je reouvre la tentative #10 que j'avais pris soin de mettre de coté et je teste.
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  16. #16
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 760
    Points : 10 541
    Points
    10 541
    Billets dans le blog
    21
    Par défaut
    Pour le moment le datagridview est bindé à une Datatable. L'idée serait de mettre la datatable en datasource d'un bindingsource qui serait datasource du datagridview ? avec ça je peux espérer ne plus avoir le scintillement ?
    C'est ça. Mais je viens de faire des essais dans mon coin et a priori ça ne marche pas. Il y a bien une mise à jour du DataGridView qui est demandée, mais comme la demande provient d'un autre thread, cela génère une exception. Et là, malheureusement, la méthode Invoke n'est pas accessible...

    Par contre, il doit être possible de réinitialiser le datasource du DataGridView au lieu de faire un Refresh :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    dataGridViewUDP.Invoke(new Action(() =>
    {
       object data = dataGridViewUDP.DataSource;
       dataGridViewUDP.DataSource = null; 
       dataGridViewUDP.DataSource = data;
    }));
    L'affectation de null à la propriété DataSource est importante et indispensable. Sans cela, tu affecterais la même DataSource, et le contenu ne serait alors pas rechargé. Tandis qu'en affectant la valeur null juste avant, quand tu réaffectes la source initiale, le composant la recharge entièrement.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  17. #17
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    Moi je viens de réussir à avoir mon exception avec
    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
    public void UDPListener(int portEcoute)
            {
                    Task.Run(async () =>
                    {
                        try
                        {
                            using (var udpClient = new UdpClient(portEcoute))
                            {
                                string loggingEvent = "";
     
                                while (true)
                                {
                                    var receivedResults = await udpClient.ReceiveAsync();
     
                                    loggingEvent += Encoding.ASCII.GetString(receivedResults.Buffer);
     
                                    ReceptPeriphUDP(loggingEvent, receivedResults.RemoteEndPoint.Address.ToString());
     
                                    TableChangedEventArgs e = new TableChangedEventArgs("Fait");
                                    if (e != null) OnTableUDPChanged(this, e);
     
                                    loggingEvent = "";
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Erreur dans suivi des périphériques \r\n" + ex.ToString(),
                                "Erreur dans suivi des périphériques",
                                MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                        }
     
                    });
     
            }
    effectivement ça le lève pas l'exception si le try catch est en dehors de la task, j'aurais pu chercher un bon moment...

    On est d'accord que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    dataGridViewUDP.Invoke(new Action(() =>
    {
       object data = dataGridViewUDP.DataSource;
       dataGridViewUDP.DataSource = null; 
       dataGridViewUDP.DataSource = data;
    }));
    c'est une solution qui nécessite que je passe le datagridview en paramètre à ma classe, ça remplace la solution avec event. C'est ça ?

    Merci
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  18. #18
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 760
    Points : 10 541
    Points
    10 541
    Billets dans le blog
    21
    Par défaut
    c'est une solution qui nécessite que je passe le datagridview en paramètre à ma classe, ça remplace la solution avec event. C'est ça ?
    Non, c'est toujours avec la solution via un event. C'est dans la méthode MyUDP_OnTableUDPChanged qu'il faut placer ce code, à la place de l'appel à la méthode Refresh() sur le DataGridView.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  19. #19
    Membre chevronné Avatar de petitours
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Février 2003
    Messages
    1 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 937
    Points : 2 018
    Points
    2 018
    Par défaut
    L'avantage c'est juste de bien séparer le métier de l'interface, c'est ca ?
    Parce que avec un invoke on est pareil obligé d'accéder à un autre thread.

    là j'ai testé le remplacement du refresh (sans event, en passant le datagridview à la classe) et ca va nettement mieux pour le scintillement.
    Seul soucis, le formatage du datagridview est reinitialisé à chque fois.
    Largeur de colonne, format du champ DateTime..., ca ajoute du monde dans le invoke que l'on doit "inutilement" appeler à chaque modification du datasource.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     m_Dgv.Invoke((Action)(() =>
                    {
                        m_Dgv.DataSource = null;
                        m_Dgv.DataSource = TableUDP;
                        m_Dgv.Columns["Date dernière vue"].DefaultCellStyle.Format = "dd/MM/yy hh:mm:ss";
                    }));
    allé, je remets l'event
    Il y a 10 sortes de personnes dans le monde : ceux qui comprennent le binaire et les autres

  20. #20
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 760
    Points : 10 541
    Points
    10 541
    Billets dans le blog
    21
    Par défaut
    L'avantage c'est juste de bien séparer le métier de l'interface, c'est ca ?
    Oui, c'est ça

    là j'ai testé le remplacement du refresh (sans event, en passant le datagridview à la classe) et ca va nettement mieux pour le scintillement.
    Seul soucis, le formatage du datagridview est reinitialisé à chque fois.
    Largeur de colonne, format du champ DateTime..., ca ajoute du monde dans le invoke que l'on doit "inutilement" appeler à chaque modification du datasource.
    Peut être pourrais-tu gérer toi-même les colonnes de ton DataGridView au lieu de te baser sur le DataTable ? Et donc mettre la propriété AutoGenerateColumns de ton DataGridView à false. Ainsi, la structure pourrais peut-être être conservée.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [ArrayList] contains() sur une liste de String
    Par nicotine002 dans le forum Collection et Stream
    Réponses: 4
    Dernier message: 18/01/2008, 19h19
  2. affichage d'une list de String
    Par ganga dans le forum iReport
    Réponses: 1
    Dernier message: 27/11/2007, 10h01
  3. récupérer la partie commune d'une liste de String
    Par Kanter dans le forum Delphi
    Réponses: 11
    Dernier message: 18/04/2007, 12h46
  4. Réponses: 23
    Dernier message: 08/06/2006, 15h06
  5. Comparaison d'une liste de string à un string
    Par maxazac dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 19/10/2005, 15h39

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