Listview : tri mono ou multicolonnes programmé ou par click de colonne tenant compte du type de données à trier

Description

Ayant pas mal cherché des sources pour faire des tris de colonnes de ListView, j'ai réalisé mon gestionnaire de tri que je mets à disposition de la communauté.
Pour l'utiliser, il faut utiliser la classe LVT_ListViewSort en référençant un ListView existant. Cette classe ajoute un événement ColumnClick pour gérer le tri d'une colonne par le clique de son entête. Cette classe contient également une propriété colSorter de la classe LVT_ListViewColSorter gérant le tri mono ou multicolonnes.
Il est également possible de spécifier des type à trier (string, numérique, date, time, datetime)
Par programmation, on peut désigner les colonnes à trier de plusieurs manières :
1) Façon WINDEV par les numéros de colonnes : WDColToSortByNum("+0,-1,+5"). +=tri ascendant, -=tri descendant
2) Façon WINDEV par titre de colonnes : WDColToSortByName("+Nom,-,+Date de naissance","-heure").
3) De façon séquentielle : AddColToSort(0,Sorter.Ascending)

Bref, tout est dans l'exemple.

Source / Exemple :


/*

  • Created by SharpDevelop.
  • User: Luis
  • Date: 12/04/2008
  • Time: 07:06
  • To change this template use Tools | Options | Coding | Edit Standard Headers.
  • /
using System; using System.Collections.Generic; using System.Collections; using System.Drawing; using System.Windows.Forms; namespace ListViewTools { /// <summary> /// Type de données à trier /// </summary> public enum eTypeToSort {tts_string=0, tts_num, tts_date, tts_time, tts_datetime}; /// <summary> /// type de donnée et ordre de tri pour une colonne /// </summary> public class LVT_ColTypeOrder { private eTypeToSort type; private SortOrder order; public eTypeToSort Type { get { return type; } } public SortOrder Order { get { return order; } } /// <summary> /// Constructeur par défaut. (string et pas de tri) /// </summary> public LVT_ColTypeOrder() { type= eTypeToSort.tts_string; order= SortOrder.None; } /// <summary> /// Constructeur. Affecte le type et l'ordre de tri. /// </summary> /// <param name="t">Type de donnée attendu</param> /// <param name="so"></param> public LVT_ColTypeOrder(eTypeToSort t, SortOrder so) { type=t; order=so; } /// <summary> /// Constructeur. Affecte le type et pas de tri. /// </summary> /// <param name="t">Type de donnée attendu</param> public LVT_ColTypeOrder(eTypeToSort t) { type=t; order= SortOrder.None; } /// <summary> /// Affecte un type. /// </summary> /// <param name="t">Type de donnée attendu</param> public void SetType(eTypeToSort t) { type=t; } /// <summary> /// Affecte un ordre. /// </summary> /// <param name="s">Type d'ordre</param> public void SetOrder (SortOrder s) { order=s; } } /// <summary> /// Colonne à trier /// </summary> public class LVT_ColToSort { private int colNum; private SortOrder order; /// <summary> /// Obtient le numéro de colonne à trier /// </summary> public int ColNum { get { return colNum; } } /// <summary> /// Obtient le type ordre de tri /// </summary> public SortOrder Order { get { return order; } } /// <summary> /// Constructeur. Affecte le numéro de colonne et le type d'ordre de tri. /// </summary> /// <param name="ncol"></param> /// <param name="so"></param> public LVT_ColToSort(int ncol, SortOrder so) { colNum=ncol; order=so; } } /// <summary> /// Gestionnaire de tri. /// </summary> public class LVT_ListViewColSorter : IComparer { private ListView lv; private List<LVT_ColTypeOrder> colTypeOrders=null; private List<LVT_ColToSort> colToSortList=null; private int genFactor=1; private SortOrder monoColOrder=SortOrder.None; private int monoCurrentCol=0; private bool monoColSortMode=true; public SortOrder MonoColOrder { get { return monoColOrder; } set { monoColOrder=value; } } public int MonoCurrentCol { get { return monoCurrentCol; } set { monoCurrentCol=value; } } public bool MonoColSortMode { get { return monoColSortMode; } set { monoColSortMode = value; } } /// <summary> /// Constructeur. Gère le tri de colonne. /// </summary> /// <param name="l">ListView à gérer</param> /// <remarks>Initialise le type de chaque colonne à string</remarks> public LVT_ListViewColSorter(ListView l) { lv=l; colTypeOrders=new List<LVT_ColTypeOrder>(); colToSortList=new List<LVT_ColToSort>(); ReinitColsTypeOrder(); } /// <summary> /// Déclare toutes les colonnes présentes en type string et pas d'ordre de tri. /// </summary> public void ReinitColsTypeOrder() { colTypeOrders.Clear(); if (lv.Columns.Count!=0) { for (int i=0; i<lv.Columns.Count; i++) { colTypeOrders.Add(new LVT_ColTypeOrder()); } } } /// <summary> /// Affecte un type de donnée à une colonne /// </summary> /// <param name="colnum">Numéro de colonne</param> /// <param name="t">Type de donnée à trier</param> public void SetColType(int colnum, eTypeToSort t) { if (colTypeOrders.Count!=lv.Columns.Count) throw new ApplicationException("Le nombre de colonnes n'a pas été correctement informé"); try { colTypeOrders[colnum].SetType(t); } catch (System.ArgumentOutOfRangeException e) { MessageBox.Show("Le numéro de colonne ["+colnum+"] est incorrect\n"+e.Message,"Erreur LVT_ListViewColSorter.SetColType"); Application.Exit(); } } /// <summary> /// Retire une colonne du ListView et du gestionnaire de tri /// </summary> /// <param name="ncol">Numéro de colonne</param> /// <remarks>Retire la colonne à trier si nécessaire</remarks> public void RemoveColumn(int ncol) { LVT_ColToSort cts; try { lv.Columns.RemoveAt(ncol); colTypeOrders.RemoveAt(ncol); for (int i=0;i<colToSortList.Count;i++) { cts=colToSortList[i]; if (cts.ColNum==ncol) { colToSortList.RemoveAt(i); break; } } } catch (System.ArgumentOutOfRangeException e) { MessageBox.Show("Numéro de colonne ["+ncol+"] incorrect\n"+e.Message,"Erreur LVT_ListViewColSorter.RemoveColomn"); Application.Exit(); } } /// <summary> /// Ajoute une colonne à droite dans le ListView et un type de donnée à trier. /// </summary> /// <param name="name">Nom de l'entête de colonne</param> /// <param name="t">Type de donnée à trier</param> public void AddColumn(string name, eTypeToSort t) { lv.Columns.Add(name); colTypeOrders.Add(new LVT_ColTypeOrder(t)); } /// <summary> /// Insère une colonne dans le ListView en précisant son nom et son type de donnée /// </summary> /// <param name="ncol">Index de la colonne à inserer</param> /// <param name="name">Nom de l'entête de colonne</param> /// <param name="t">Type de donnée à trier</param> public void InsertColumn(int ncol, string name, eTypeToSort t) { LVT_ColToSort cts; List<LVT_ColToSort> tmp=new List<LVT_ColToSort>(); try { lv.Columns.Insert(ncol,name); for (int i=0;i<colToSortList.Count;i++) { cts=colToSortList[i]; if (cts.ColNum>=ncol) tmp.Add(new LVT_ColToSort(cts.ColNum+1,cts.Order)); else tmp.Add(new LVT_ColToSort(cts.ColNum,cts.Order)); } colToSortList=tmp; colTypeOrders.Insert(ncol,new LVT_ColTypeOrder(t)); } catch (System.ArgumentOutOfRangeException e) { MessageBox.Show("Erreur dans LVT_ListViewColSorter.InsertColomn\n"+e.Message,"Erreur"); Application.Exit(); } } /// <summary> /// Désigne l'unique colonne à trier en mode muticolonnes /// </summary> /// <param name="ncol">Numéro de la colonne</param> /// <param name="so">Type de donnée et ordre de tri</param> public void SetOneColToSort(int ncol, SortOrder so) { colToSortList.Clear(); colToSortList.Add(new LVT_ColToSort(ncol,so)); } /// <summary> /// Ajoute un colonne à trier en mode multicolonnes /// </summary> /// <param name="ncol">Numéro de la colonne</param> /// <param name="so">Type de donnée et ordre de tri</param> public void AddColToSort(int ncol, SortOrder so) { foreach (LVT_ColToSort cs in colToSortList) { if (cs.ColNum==ncol) throw new ApplicationException("La colonne "+ncol.ToString()+" a déjà été spécifiée"); } colToSortList.Add(new LVT_ColToSort(ncol,so)); } /// <summary> /// Obtient le numéro de la colonne de lv en fonction de son libellé /// </summary> /// <param name="name">libellé de la colonne à cherche</param> /// <returns>numéro de la colonne trouvée ou, en cas d'échec, -1</returns> private int getColByName(string name) { foreach (ColumnHeader c in lv.Columns) { if (c.Text==name) return c.Index; } return -1; } /// <summary> /// Création de la liste des colonnes à trier façon WinDEV /// selon le libéllé de colonne. /// Format : [+|-]libellé1,libellé2,... /// + ou rien : tri ascendant /// - : tri descendan. /// </summary> /// <param name="wdcmd">Commande</param> public void WDColToSortByName(string wdcmd) { SortOrder so; int cnum=-1; string name; string[] cmds=wdcmd.Split(new Char[] {','}); List<LVT_ColToSort> ctsl=new List<LVT_ColToSort>(); foreach(string cmd in cmds) { if (!String.IsNullOrEmpty(cmd)) { if (cmd.StartsWith("-")) { so= SortOrder.Descending; name=cmd.Substring(1); } else if (cmd.StartsWith("+")) { so= SortOrder.Ascending; name=cmd.Substring(1); } else { so= SortOrder.Ascending; name=cmd; } cnum=getColByName(name); if (cnum==-1) throw new ApplicationException("Le nom de colonne "+name+" n'existe pas"); ctsl.Add(new LVT_ColToSort(cnum,so)); } } colToSortList=ctsl; } /// <summary> /// Création d'une liste de colonnes à trier façon WINDEV /// selon le numéro de colonne /// Format : [+|-]num1,num2,... /// + ou rien : tri ascendant /// - : tri descendant /// </summary> /// <param name="wdcmd"></param> public void WDColToSortByNum(string wdcmd) { SortOrder so; int cnum=-1; string snum; string[] cmds=wdcmd.Split(new Char[] {','}); List<LVT_ColToSort> ctsl=new List<LVT_ColToSort>(); foreach(string cmd in cmds) { if (!String.IsNullOrEmpty(cmd)) { if (cmd.StartsWith("-")) { so= SortOrder.Descending; snum=cmd.Substring(1); } else if (cmd.StartsWith("+")) { so= SortOrder.Ascending; snum=cmd.Substring(1); } else { so= SortOrder.Ascending; snum=cmd; } if (!int.TryParse(snum,out cnum)) throw new ApplicationException("L'index de colonne "+snum+" n'est pas un entier"); if ((cnum<0) || (cnum>lv.Columns.Count-1)) throw new ApplicationException("L'index de colone ["+snum+"] est hors limite"); ctsl.Add(new LVT_ColToSort(cnum,so)); } } colToSortList=ctsl; } /// <summary> /// Vide la liste des colonnes à trier en mode multicolonnes /// </summary> public void ResetColsToSort() { colToSortList.Clear(); } /// <summary> /// Retourne la commande envoyée par WDColToSortByName /// Tient compte des modifications de colonnes /// </summary> /// <returns>chaine sous forme +nom1,-nom2,...</returns> public string GetWDColsToSortNames() { string res=""; string cmd=""; foreach (LVT_ColToSort cts in colToSortList) { cmd=((cts.Order== SortOrder.Descending) ? "-" : "+")+lv.Columns[cts.ColNum].Text; if (String.IsNullOrEmpty(res)) res=cmd; else res+=","+cmd; } return res; } /// <summary> /// Retourne la commande envoyée par WDColToSortByNum /// Tient compte des modification de colonnnes /// </summary> /// <returns>Chaine sous forme +0,-1,....</returns> public string GetWDColsToSortNums() { string res=""; string cmd=""; foreach (LVT_ColToSort cts in colToSortList) { cmd=((cts.Order== SortOrder.Descending) ? "-" : "+")+cts.ColNum.ToString(); if (String.IsNullOrEmpty(res)) res=cmd; else res+=","+cmd; } return res; } /// <summary> /// Tri en mode multicolonnes /// </summary> private void sort(int rf) { monoColSortMode=false; genFactor=rf; if (colToSortList.Count==0) return; lv.Sort(); } /// <summary> /// Tri dans l'ordre choisi /// </summary> public void Sort() { this.sort(1); } /// <summary> /// Tri dans l'ordre inverse de l'ordre choisi /// </summary> public void ReversedSort() { this.sort(-1); } /// <summary> /// Comparaison de donnée selon son type /// </summary> /// <param name="Text1">Texte 1</param> /// <param name="Text2">Texte 2</param> /// <param name="to">type de donnée et type d'ordre</param> /// <param name="factor">1 pour tri ascendant -1 pour tri descendant</param> /// <returns></returns> private int compareByType(string Text1,string Text2,LVT_ColTypeOrder to, int factor) { if (to.Type== eTypeToSort.tts_string) { return factor*Text1.CompareTo(Text2); } if (to.Type== eTypeToSort.tts_num) { double v1=0; double v2=0; double.TryParse(Text1,out v1); double.TryParse(Text2,out v2); return factor*v1.CompareTo(v2); } if ((to.Type== eTypeToSort.tts_date) || (to.Type== eTypeToSort.tts_datetime)) { DateTime date1; DateTime date2; date1=Convert.ToDateTime(Text1); date2=Convert.ToDateTime(Text2); return factor*date1.CompareTo(date2); } if (to.Type== eTypeToSort.tts_time) { TimeSpan time1; TimeSpan time2; TimeSpan.TryParse(Text1,out time1); TimeSpan.TryParse(Text2,out time2); return factor*time1.CompareTo(time2); } return 0; } /// <summary> /// Fonction recursive de comparaison de données en multicolonne /// </summary> /// <param name="it1">ListViewItem1</param> /// <param name="it2">ListViewItem2</param> /// <param name="n">Index dans la liste des colonnes à trier</param> /// <param name="niv">Niveau de récursivité</param> /// <returns></returns> private int compareMultiCol(ListViewItem it1, ListViewItem it2, int n, int niv) { int factor=(colToSortList[n].Order== SortOrder.Ascending) ? 1 : -1; factor*=genFactor; int res=compareByType(it1.SubItems[colToSortList[n].ColNum].Text,it2.SubItems[colToSortList[n].ColNum].Text,colTypeOrders[colToSortList[n].ColNum],factor); if (niv!=0) { if (res==0) // si égalité on trie la colonne suivante return compareMultiCol(it1,it2,n+1,niv-1); } return res; } /// <summary> /// Héritage de IComparer /// </summary> /// <param name="o1">Objet 1</param> /// <param name="o2">Objet 2</param> /// <returns></returns> public int Compare(object o1, object o2) { ListViewItem it1=(ListViewItem) o1; ListViewItem it2=(ListViewItem) o2; int factor=1; if (monoColSortMode) { // tri colonne unique géré par ColumnClick if (monoColOrder== SortOrder.None) return 0; if (monoColOrder== SortOrder.Descending) factor=-1; return compareByType(it1.SubItems[monoCurrentCol].Text,it2.SubItems[monoCurrentCol].Text,colTypeOrders[monoCurrentCol],factor); } if (colToSortList.Count==0) return 0; return compareMultiCol(it1,it2,0,colToSortList.Count-1); } } /// <summary> /// Initialisation du gestionnaire de tri pour un ListView /// </summary> public class LVT_ListViewSort { private ListView lv=null; private static List<ListView> lvs=new List<ListView>(); private LVT_ListViewColSorter colSorter=null; public LVT_ListViewColSorter ColSorter { get { return colSorter; } } /// <summary> /// Constructeur. Met en place les mécanismes de tri de colonne ListView /// </summary> /// <param name="l">ListView à gérer</param> /// <remarks>Un list view ne peut être géré qu'une fois</remarks> public LVT_ListViewSort(ListView l) { if (lvs.Contains(l)) throw new ApplicationException("ListView déjà utilisé par la classe LVT_ListViewSortInit"); lv=l; colSorter=new LVT_ListViewColSorter(l); l.ListViewItemSorter=colSorter; // creer les event lv.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.OnColumnClick); } // évenements /// <summary> /// Evènnement ColumnClick pour la gestion de tri d'une colonne demandée par l'utilisateur /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public void OnColumnClick(object sender, ColumnClickEventArgs e) { int col=colSorter.MonoCurrentCol; colSorter.MonoColSortMode=true; if (e.Column==col) { if ((colSorter.MonoColOrder== SortOrder.Descending) || (colSorter.MonoColOrder== SortOrder.None)) colSorter.MonoColOrder= SortOrder.Ascending; else colSorter.MonoColOrder= SortOrder.Descending; } else { colSorter.MonoColOrder= SortOrder.Ascending; colSorter.MonoCurrentCol=e.Column; } lv.Sort(); //colSorter.monoColSortMode=false; } } }

Conclusion :


C'est un outil perfectible (vos suggestions sont les bienvenues).
L'execution serait plus rapide sans le code managé.

J'espère néanmoins que cela puisse répondre à vos attentes.

Bonne utilisation

Codes Sources

A voir également

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.