none
Faire un tri personnalisé dans un datagridview RRS feed

  • Question

  • Bonjour.

    Je possède une colonne dans mon Dgv qui contient des adresses IP. Lorsque je clique sur l'entête pour les trier, il me trie bien les adresses IP mais de cette façon :

    192.168.1.1
    192.168.1.10
    192.168.1.11
    192.168.1.12
    192.168.1.2
    192.168.1.20
    192.168.1.21

    Je comprends bien pourquoi il fait ça, mais j'aimerai qu'il me les trie dans un vrai ordre croissant, c'est à dire :

    192.168.1.1
    192.168.1.2
    192.168.1.10
    192.168.1.11
    192.168.1.12
    192.168.1.20
    192.168.1.21

    Je ne comprend pas trop comment il faut faire pour créer un tri personnalisé dans un datagridview. Avez-vous quelques conseils à me donner ?

    Merci !

    mardi 4 août 2009 13:39

Réponses

  • Si tu as beaucoup de développement déjà effectué et automatisés via les datasets, ça peux te faire pas mal de changement. Ca te fera des solutions pour les prochaines fois :)
    Pour ma part je préfère travailler avec des objets plutot que des datasets.
    Néanmoins, voici une troisième solution 100% datasets  via LINQ. ( donc VS2008, .Net 3.5 )

    Dans ton projet référence, System.Data.DataSetExtensions

    ceci nous permet d'utiliser la méthode d'extension AsEnumerable sur un DataTable.
    Ensuite nours pourrons faire appel à la méthode OrderBy
    Cette méthode possède une surcharge qui permet de passer un IComparer. C'est dans ce IComparer que l'on implémente la méthode de tri.
    Le tri est ensuite gérer manuellement sur un click de Header de colonne :

    Donc,

    public partial class Form4 : Form
    {
      private DataTable m_table = null;
      private int m_Sort = 1;
    
      public Form4()
      {
        InitializeComponent();
        this.InitGrid();
      }
    
      public void InitGrid()
      {
        m_table = new DataTable("table1");
        m_table.Columns.Add("ip", typeof(string));
        m_table.Rows.Add("192.168.1.1");
        m_table.Rows.Add("192.168.1.10");
        m_table.Rows.Add("192.168.1.11");
        m_table.Rows.Add("192.168.1.12");
        m_table.Rows.Add("192.168.1.2");
        m_table.Rows.Add("192.168.1.20");
        m_table.Rows.Add("192.168.1.21");
    
        this.dataGridView1.DataSource = m_table;
    
      }
    
      private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
      {
        if (this.m_table != null)
        {
          // obtient une vue triée par le comparer
          DataView view = this.m_table.AsEnumerable().OrderBy(a => a, new MyComparer(this.m_Sort)).AsDataView();
    
          // affecte la vue
          this.dataGridView1.DataSource = view;
    
          // change le tri
          this.m_Sort *= -1;
        }
      }
    }
    
    public class MyComparer : IComparer<DataRow>
    {
      int m_Sort = 0;
    
      public MyComparer(int sort)
      {
        this.m_Sort = sort;
      }
    
      #region IComparer<DataRow> Membres
    
      public int Compare(DataRow x, DataRow y)
      {
        // ip sous forme de tableaux de chaine
        String[] value1 = x.Field<string>("ip").Split('.');
        String[] value2 = y.Field<string>("ip").Split('.');
    
        int sortResult = 0;
    
        // pour cahque niveau du tableau
        for (int i = 0; i < value1.Length; i++)
        {
          // la comparaison porte sur des chiffres donc on parse
          // on arrête la boucle si on a pu comparer les éléments
          if (int.Parse(value1[i]) > int.Parse(value2[i]))
          {
            sortResult = 1;
            break;
          }
          else if (int.Parse(value1[i]) < int.Parse(value2[i]))
          {
            sortResult = -1;
            break;
          }
          else
            sortResult = 0;
        }
    
        return sortResult * this.m_Sort;
      }
    
      #endregion
    }



    • Proposé comme réponse nikhoModerator jeudi 6 août 2009 13:26
    • Marqué comme réponse AlexBesn jeudi 6 août 2009 14:19
    jeudi 6 août 2009 13:25
    Modérateur

Toutes les réponses

  • Tu peux envisager différents moyens :
     - tu utilises une list intermédiaire dans laquelle tu stocke tes ip. Sur cette liste tu réalises le tri ( avec un Comparer ou LINQ ). Tu ajoutes alors manuellement les items dans ta grid

    - tu utilises l'event SortCompare de la grid pour faire un tri customiser ou un IComparer. Tu trouveras deux exemples complets à l'adresse suivante : http://msdn.microsoft.com/en-us/library/ms171608.aspx
    mercredi 5 août 2009 09:36
    Modérateur
  • Merci de ta réponse.

    Je souhaite utiliser ta deuxième solution. J'avais déjà trouvé ce lien, mais j'avoue que je ne comprends pas tout dans le code.

    Je ne comprends pas la différence entre les deux tris, et surtout je vois que l'objet SortOrder est utilisé or on ne peut faire que du croissant ou décroissant avec cet objet. Et si je prends un tri basique croissant ou décroissant il va me ressortir la même chose que ce que j'ai montré dans mon 1er post, ce n'est pas ce que je veux.

    Pourrais-je avoir un peu plus d'explications ?
    mercredi 5 août 2009 13:08
  • Pour faire le tri tu dois donner ta propre implémentation de la méthode SortCompare. Dans les arguments de l'event tu trouves deux valeurs qui doivent être comparées.
    Ce test sera fait sur l'ensemble des valeurs de la grille et permettra de trier l'ensemble des données.
    A toi de les comparer comme tu le souhaite et de retourner les valeurs -1,0,1 en fonction du résultat de ta comparaison. La grille s'appuie sur cette valeur pour ensuite ordonner les éléments.

    Voilà un exemple pour implémenter ton tri :

    public partial class Form2 : Form
      {
        public Form2()
        {
          InitializeComponent();
    
          this.InitGrid();
    
        }
    
        private void InitGrid()
        {
          this.dataGridView1.Columns.Add("ip", "ip");
          this.dataGridView1.Rows.Add("192.168.1.1");
          this.dataGridView1.Rows.Add("192.168.1.10");
          this.dataGridView1.Rows.Add("192.168.1.11");
          this.dataGridView1.Rows.Add("192.168.1.12");
          this.dataGridView1.Rows.Add("192.168.1.2");
          this.dataGridView1.Rows.Add("192.168.1.20");
          this.dataGridView1.Rows.Add("192.168.1.21");
        }
    
        private void dataGridView1_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
        {
          // ip sous forme de tableaux de chaine
          String[] value1 = e.CellValue1.ToString().Split('.');
          String[] value2 = e.CellValue2.ToString().Split('.');
    
          int sortResult = 0;
    
          // pour cahque niveau du tableau
          for (int i = 0;  i < value1.Length; i++)
          {
            // la comparaison porte sur des chiffres donc on parse
            // on arrête la boucle si on a pu comparer les éléments
            if (int.Parse(value1[i]) > int.Parse(value2[i]))
            {
              sortResult = 1;
              break;
            }
            else if (int.Parse(value1[i]) < int.Parse(value2[i]))
            {
              sortResult = -1;
              break;
            }
            else
              sortResult = 0;
          }
    
          e.SortResult = sortResult;
          e.Handled = true;
        }
     }


    mercredi 5 août 2009 15:20
    Modérateur
  • Merci ! Ton code marche nickel, bravo !

    Par contre, je viens de voir dans MSDN que cet événement ne se déclenche pas si il y a un datasource lié au datagridview. Or le mien est lié à un dataset donc j'ai un datasource, donc fatalement l'événement ne se déclenche pas.

    Pourquoi une telle limitation ? Comment la contourner ?
    mercredi 5 août 2009 16:40
  • Lorsque tu utilises une datasource c'est elle qui porte les données c'est donc sur elle que doit porter le tri ou les filtres d'où la limitation.

    Tu peux néanmoins utiliser le SortCompare même avec ton Dataset : il suffit de boucler sur la datatable de ton dataset et d'ajouter toi même les lignes dans ta grille comme dans la méthode InitGrid(). Cette solution serait alors valable.

    Une autre solution, c'est d'utiliser un dataset qui contient un objet surlequel tu as implémenté IComparable. Comme dans le SortCompare, c'est via la méthode CompareTo que l'on implémente la logique de tri. La seule astuce est de surchargé la méthode ToString() pour afficher la valeur.

    Ci dessous un exemple avec IComparable.

    Tu pourrais aussi utilise les extensions Linq pour dataset pour effectuer un OrderBY un créant un IComparer ( même genre de code pour implémenter la logique de tri )

    Autre piste ne pas utiliser de DataSet mais des collections d'objet typés ( List<T> ) qui possèdent une méthode Sort( IComparer<T> )

    public partial class Form3 : Form
    {
      public Form3()
      {
        InitializeComponent();
        this.InitGrid();
      }
    
      private void InitGrid()
      {
        DataTable table = new DataTable("table1");
        table.Columns.Add("ip", typeof(MyIp));
        table.Rows.Add(new MyIp() { Ip = "192.168.1.1" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.10" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.11" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.12" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.2" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.20" });
        table.Rows.Add(new MyIp() { Ip = "192.168.1.21" });
    
        this.dataGridView1.DataSource = table;
      }
    }
    
    public class MyIp : IComparable
    {
      public string Ip { get; set; }
    
      public override string ToString()
      {
        return this.Ip;
      }
    
      #region IComparable<MyIp> Membres
    
      public int CompareTo(object other)
      {
        // ip sous forme de tableaux de chaine
        String[] value1 = this.Ip.ToString().Split('.');
        String[] value2 = ((MyIp)other).Ip.ToString().Split('.');
    
        int sortResult = 0;
    
        // pour cahque niveau du tableau
        for (int i = 0; i < value1.Length; i++)
        {
          // la comparaison porte sur des chiffres donc on parse
          // on arrête la boucle si on a pu comparer les éléments
          if (int.Parse(value1[i]) > int.Parse(value2[i]))
          {
            sortResult = 1;
            break;
          }
          else if (int.Parse(value1[i]) < int.Parse(value2[i]))
          {
            sortResult = -1;
            break;
          }
          else
            sortResult = 0;
        }
    
        return sortResult;
      }
    
      #endregion
    }
    jeudi 6 août 2009 09:52
    Modérateur
  • Merci beaucoup pour ton aide très rapide et très complète !

    Le problème est j'utilise beaucoup le fait que le dataset soit à la source du datagridview pour mettre à jour ma base Access et que lorsqu'on modifie des données dans le Datagridview elles soit directement répercutées dans le Dataset. En parcourant le dataset et en remplissant manuellement le datagridview je vais donc perdre ce "lien".

    J'avoue que créer un objet MyIP ne m'arrange pas trop non plus.

    Mon logiciel se connecte à une base Access, charge la table demandée dans une datatable de mon dataset, affichée via un datagridview. C'est un dataadaptateur qui se charge de tout mettre dans le dataset, donc je ne vois pas trop comment lui dire que lorsque que c'est une adresse IP, qu'il la transforme en objet MyIp.

    De plus, mon programme utilise les données qui sont dans le dataset et à chaque fois je les manipule en faisant Ds.Tables["matable"].Columns[i].Rows[i].Value.ToString(). Et quand j'update ma base je laisse le CommandBuilder s'en charger, or avec un objet MyIp je pense que ça ne fonctionne pas.

    Bref, j'ai peur qu'en créant un objet MyIp ce soit trop compliqué et que je sois obligé de reprogrammer beaucoup de choses.

    Je crois qu'il ne reste plus que la solution avec Linq, mais je n'ai jamais utilisé ce concept ...

    jeudi 6 août 2009 12:55
  • Si tu as beaucoup de développement déjà effectué et automatisés via les datasets, ça peux te faire pas mal de changement. Ca te fera des solutions pour les prochaines fois :)
    Pour ma part je préfère travailler avec des objets plutot que des datasets.
    Néanmoins, voici une troisième solution 100% datasets  via LINQ. ( donc VS2008, .Net 3.5 )

    Dans ton projet référence, System.Data.DataSetExtensions

    ceci nous permet d'utiliser la méthode d'extension AsEnumerable sur un DataTable.
    Ensuite nours pourrons faire appel à la méthode OrderBy
    Cette méthode possède une surcharge qui permet de passer un IComparer. C'est dans ce IComparer que l'on implémente la méthode de tri.
    Le tri est ensuite gérer manuellement sur un click de Header de colonne :

    Donc,

    public partial class Form4 : Form
    {
      private DataTable m_table = null;
      private int m_Sort = 1;
    
      public Form4()
      {
        InitializeComponent();
        this.InitGrid();
      }
    
      public void InitGrid()
      {
        m_table = new DataTable("table1");
        m_table.Columns.Add("ip", typeof(string));
        m_table.Rows.Add("192.168.1.1");
        m_table.Rows.Add("192.168.1.10");
        m_table.Rows.Add("192.168.1.11");
        m_table.Rows.Add("192.168.1.12");
        m_table.Rows.Add("192.168.1.2");
        m_table.Rows.Add("192.168.1.20");
        m_table.Rows.Add("192.168.1.21");
    
        this.dataGridView1.DataSource = m_table;
    
      }
    
      private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
      {
        if (this.m_table != null)
        {
          // obtient une vue triée par le comparer
          DataView view = this.m_table.AsEnumerable().OrderBy(a => a, new MyComparer(this.m_Sort)).AsDataView();
    
          // affecte la vue
          this.dataGridView1.DataSource = view;
    
          // change le tri
          this.m_Sort *= -1;
        }
      }
    }
    
    public class MyComparer : IComparer<DataRow>
    {
      int m_Sort = 0;
    
      public MyComparer(int sort)
      {
        this.m_Sort = sort;
      }
    
      #region IComparer<DataRow> Membres
    
      public int Compare(DataRow x, DataRow y)
      {
        // ip sous forme de tableaux de chaine
        String[] value1 = x.Field<string>("ip").Split('.');
        String[] value2 = y.Field<string>("ip").Split('.');
    
        int sortResult = 0;
    
        // pour cahque niveau du tableau
        for (int i = 0; i < value1.Length; i++)
        {
          // la comparaison porte sur des chiffres donc on parse
          // on arrête la boucle si on a pu comparer les éléments
          if (int.Parse(value1[i]) > int.Parse(value2[i]))
          {
            sortResult = 1;
            break;
          }
          else if (int.Parse(value1[i]) < int.Parse(value2[i]))
          {
            sortResult = -1;
            break;
          }
          else
            sortResult = 0;
        }
    
        return sortResult * this.m_Sort;
      }
    
      #endregion
    }



    • Proposé comme réponse nikhoModerator jeudi 6 août 2009 13:26
    • Marqué comme réponse AlexBesn jeudi 6 août 2009 14:19
    jeudi 6 août 2009 13:25
    Modérateur
  • Fiou ... Alors là je dois dire respect, t'es vraiment un boss !

    J'ai juste modifié la fonction Compare car certaines entrées ne contiennent pas d'IP et ça fait planter le programme. Je la poste pour que tu me dises si c'est bien ou non de faire comme ça :

    public int Compare(DataRow x, DataRow y)
            {
                int sortResult = -2;
    
                //On vérifie s'il y des adresses IP vides
                if (x["ADRIP"].ToString().Trim() == "" && y["ADRIP"].ToString().Trim() == "")
                    sortResult = 0;
                else if (x["ADRIP"].ToString().Trim() == "")
                    sortResult = -1;
                else if (y["ADRIP"].ToString().Trim() == "")
                    sortResult = 1;
    
                //S'il y a une adresse IP de vide on retourne le resultat du tri
                if(sortResult!=-2)
                    return sortResult * this.m_Sort;
    
    
                //ip sous forme de tableaux de chaine
                String[] value1 = x.Field<string>("ADRIP").Split('.');
                String[] value2 = y.Field<string>("ADRIP").Split('.');
    
                
    
                // pour cahque niveau du tableau
                for (int i = 0; i < value1.Length; i++)
                {
                    // la comparaison porte sur des chiffres donc on parse
                    // on arrête la boucle si on a pu comparer les éléments
                    if (int.Parse(value1[i]) > int.Parse(value2[i]))
                    {
                        sortResult = 1;
                        break;
                    }
                    else if (int.Parse(value1[i]) < int.Parse(value2[i]))
                    {
                        sortResult = -1;
                        break;
                    }
                    else
                        sortResult = 0;
                }
    
                return sortResult * this.m_Sort;
            }
    Merci merci merci pour tout !!!
    jeudi 6 août 2009 14:21
  • Pas de quoi. Oui ça me parait bien
    jeudi 6 août 2009 14:29
    Modérateur