Currently I use a custom sorter on the listview, and i can sort the listview each time i click on the FIRST column, but it won't sort by other columns.
SortStyle: Variable to determine whether it is Ascending Sort, or Descending.
if (e.Column == 0)开发者_Python百科
{
if (SortStyle == 0)
{
List.ListViewItemSorter = customSortDsc;
SortStyle = 1;
}
else
{
List.ListViewItemSorter = customSortAsc;
SortStyle = 0;
}
}
This works fine when sorting for the first column, but if you were to do it on any other column, it would just sort by the first column. Is there a way to sort by the column clicked?
Forget about your custom sorter. Start over using the code at the following page. It will show you how to define a class that inherits from the IComparer interface. Each line is commented out, so you can actually see what is happening. The only potential complication is how you are retrieving your Listview Items from your Listview control. Get those squared away and all you need to do is copy and paste the IComparer interface class and the columnClick method.
http://support.microsoft.com/kb/319401
If you are starting out with a ListView, do yourself a huge favour and use an ObjectListView instead. ObjectListView is an open source wrapper around .NET WinForms ListView, which makes the ListView much easier to use and solves lots of common problems for you. Sorting by column click is one of the many things it handles for you automatically.
Seriously, you will never regret using an ObjectListView instead of a normal ListView.
- ObjectListView Home Page
- ObjectListView Nuget Package
I sort using column name to set any sorting specifics that may need to be handled based on data type stored in the column and or if the column has already been sorted on(asc/desc). Here's a snippet from my ColumnClick event handler.
private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
{
ListViewItemComparer sorter = GetListViewSorter(e.Column);
listView.ListViewItemSorter = sorter;
listView.Sort();
}
private ListViewItemComparer GetListViewSorter(int columnIndex)
{
ListViewItemComparer sorter = (ListViewItemComparer)listView.ListViewItemSorter;
if (sorter == null)
{
sorter = new ListViewItemComparer();
}
sorter.ColumnIndex = columnIndex;
string columnName = packagedEstimateListView.Columns[columnIndex].Name;
switch (columnName)
{
case ApplicationModel.DisplayColumns.DateCreated:
case ApplicationModel.DisplayColumns.DateUpdated:
sorter.ColumnType = ColumnDataType.DateTime;
break;
case ApplicationModel.DisplayColumns.NetTotal:
case ApplicationModel.DisplayColumns.GrossTotal:
sorter.ColumnType = ColumnDataType.Decimal;
break;
default:
sorter.ColumnType = ColumnDataType.String;
break;
}
if (sorter.SortDirection == SortOrder.Ascending)
{
sorter.SortDirection = SortOrder.Descending;
}
else
{
sorter.SortDirection = SortOrder.Ascending;
}
return sorter;
}
Below is my ListViewItemComparer
public class ListViewItemComparer : IComparer
{
private int _columnIndex;
public int ColumnIndex
{
get
{
return _columnIndex;
}
set
{
_columnIndex = value;
}
}
private SortOrder _sortDirection;
public SortOrder SortDirection
{
get
{
return _sortDirection;
}
set
{
_sortDirection = value;
}
}
private ColumnDataType _columnType;
public ColumnDataType ColumnType
{
get
{
return _columnType;
}
set
{
_columnType = value;
}
}
public ListViewItemComparer()
{
_sortDirection = SortOrder.None;
}
public int Compare(object x, object y)
{
ListViewItem lviX = x as ListViewItem;
ListViewItem lviY = y as ListViewItem;
int result;
if (lviX == null && lviY == null)
{
result = 0;
}
else if (lviX == null)
{
result = -1;
}
else if (lviY == null)
{
result = 1;
}
switch (ColumnType)
{
case ColumnDataType.DateTime:
DateTime xDt = DataParseUtility.ParseDate(lviX.SubItems[ColumnIndex].Text);
DateTime yDt = DataParseUtility.ParseDate(lviY.SubItems[ColumnIndex].Text);
result = DateTime.Compare(xDt, yDt);
break;
case ColumnDataType.Decimal:
Decimal xD = DataParseUtility.ParseDecimal(lviX.SubItems[ColumnIndex].Text.Replace("$", string.Empty).Replace(",", string.Empty));
Decimal yD = DataParseUtility.ParseDecimal(lviY.SubItems[ColumnIndex].Text.Replace("$", string.Empty).Replace(",", string.Empty));
result = Decimal.Compare(xD, yD);
break;
case ColumnDataType.Short:
short xShort = DataParseUtility.ParseShort(lviX.SubItems[ColumnIndex].Text);
short yShort = DataParseUtility.ParseShort(lviY.SubItems[ColumnIndex].Text);
result = xShort.CompareTo(yShort);
break;
case ColumnDataType.Int:
int xInt = DataParseUtility.ParseInt(lviX.SubItems[ColumnIndex].Text);
int yInt = DataParseUtility.ParseInt(lviY.SubItems[ColumnIndex].Text);
return xInt.CompareTo(yInt);
break;
case ColumnDataType.Long:
long xLong = DataParseUtility.ParseLong(lviX.SubItems[ColumnIndex].Text);
long yLong = DataParseUtility.ParseLong(lviY.SubItems[ColumnIndex].Text);
return xLong.CompareTo(yLong);
break;
default:
result = string.Compare(
lviX.SubItems[ColumnIndex].Text,
lviY.SubItems[ColumnIndex].Text,
false);
break;
}
if (SortDirection == SortOrder.Descending)
{
return -result;
}
else
{
return result;
}
}
}
Made minor changes to the article here to accommodate sorting of both string and numeric values in ListView.
Form1.cs contains
using System;
using System.Windows.Forms;
namespace ListView
{
public partial class Form1 : Form
{
Random rnd = new Random();
private ListViewColumnSorter lvwColumnSorter;
public Form1()
{
InitializeComponent();
// Create an instance of a ListView column sorter and assign it to the ListView control.
lvwColumnSorter = new ListViewColumnSorter();
this.listView1.ListViewItemSorter = lvwColumnSorter;
InitListView();
}
private void InitListView()
{
listView1.View = View.Details;
listView1.GridLines = true;
listView1.FullRowSelect = true;
//Add column header
listView1.Columns.Add("Name", 100);
listView1.Columns.Add("Price", 70);
listView1.Columns.Add("Trend", 70);
for (int i = 0; i < 10; i++)
{
listView1.Items.Add(AddToList("Name" + i.ToString(), rnd.Next(1, 100).ToString(), rnd.Next(1, 100).ToString()));
}
}
private ListViewItem AddToList(string name, string price, string trend)
{
string[] array = new string[3];
array[0] = name;
array[1] = price;
array[2] = trend;
return (new ListViewItem(array));
}
private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
{
// Determine if clicked column is already the column that is being sorted.
if (e.Column == lvwColumnSorter.SortColumn)
{
// Reverse the current sort direction for this column.
if (lvwColumnSorter.Order == SortOrder.Ascending)
{
lvwColumnSorter.Order = SortOrder.Descending;
}
else
{
lvwColumnSorter.Order = SortOrder.Ascending;
}
}
else
{
// Set the column number that is to be sorted; default to ascending.
lvwColumnSorter.SortColumn = e.Column;
lvwColumnSorter.Order = SortOrder.Ascending;
}
// Perform the sort with these new sort options.
this.listView1.Sort();
}
}
}
ListViewColumnSorter.cs contains
using System;
using System.Collections;
using System.Windows.Forms;
/// <summary>
/// This class is an implementation of the 'IComparer' interface.
/// </summary>
public class ListViewColumnSorter : IComparer
{
/// <summary>
/// Specifies the column to be sorted
/// </summary>
private int ColumnToSort;
/// <summary>
/// Specifies the order in which to sort (i.e. 'Ascending').
/// </summary>
private SortOrder OrderOfSort;
/// <summary>
/// Case insensitive comparer object
/// </summary>
private CaseInsensitiveComparer ObjectCompare;
/// <summary>
/// Class constructor. Initializes various elements
/// </summary>
public ListViewColumnSorter()
{
// Initialize the column to '0'
ColumnToSort = 0;
// Initialize the sort order to 'none'
OrderOfSort = SortOrder.None;
// Initialize the CaseInsensitiveComparer object
ObjectCompare = new CaseInsensitiveComparer();
}
/// <summary>
/// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison.
/// </summary>
/// <param name="x">First object to be compared</param>
/// <param name="y">Second object to be compared</param>
/// <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
public int Compare(object x, object y)
{
int compareResult;
ListViewItem listviewX, listviewY;
// Cast the objects to be compared to ListViewItem objects
listviewX = (ListViewItem)x;
listviewY = (ListViewItem)y;
decimal num = 0;
if (decimal.TryParse(listviewX.SubItems[ColumnToSort].Text, out num))
{
compareResult = decimal.Compare(num, Convert.ToDecimal(listviewY.SubItems[ColumnToSort].Text));
}
else
{
// Compare the two items
compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
}
// Calculate correct return value based on object comparison
if (OrderOfSort == SortOrder.Ascending)
{
// Ascending sort is selected, return normal result of compare operation
return compareResult;
}
else if (OrderOfSort == SortOrder.Descending)
{
// Descending sort is selected, return negative result of compare operation
return (-compareResult);
}
else
{
// Return '0' to indicate they are equal
return 0;
}
}
/// <summary>
/// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
/// </summary>
public int SortColumn
{
set
{
ColumnToSort = value;
}
get
{
return ColumnToSort;
}
}
/// <summary>
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
/// </summary>
public SortOrder Order
{
set
{
OrderOfSort = value;
}
get
{
return OrderOfSort;
}
}
}
i used this trick
private void lv_TavComEmpty_ColumnClick(object sender, ColumnClickEventArgs e)
{
ListView lv = (ListView)sender;
//propriety SortOrder make me some problem on graphic layout
//i use this tag to set last order
if (lv.Tag == null || (int)lv.Tag > 0)
//if (lv.Sorting == SortOrder.Ascending)
{
ListViewItem[] tmp = lv.Items.Cast<ListViewItem>().OrderBy(t => t.SubItems[e.Column].Text).ToArray();
lv.Items.Clear();
lv.Items.AddRange(tmp);
lv.Tag = -1;
//lv.Sorting = SortOrder.Descending;
}
else
{
ListViewItem[] tmp = lv.Items.Cast<ListViewItem>().OrderByDescending(t => t.SubItems[e.Column].Text).ToArray();
lv.Items.Clear();
lv.Items.AddRange(tmp);
lv.Tag = +1;
//lv.Sorting = SortOrder.Ascending;
}
}
My solution is a class to sort listView items when you click on column header.
You can specify the type of each column.
listView.ListViewItemSorter = new ListViewColumnSorter();
listView.ListViewItemSorter.ColumnsTypeComparer.Add(0, DateTime);
listView.ListViewItemSorter.ColumnsTypeComparer.Add(1, int);
That's it !
The C# class :
using System.Collections;
using System.Collections.Generic;
using EDV;
namespace System.Windows.Forms
{
/// <summary>
/// Cette classe est une implémentation de l'interface 'IComparer' pour le tri des items de ListView. Adapté de http://support.microsoft.com/kb/319401.
/// </summary>
/// <remarks>Intégré par EDVariables.</remarks>
public class ListViewColumnSorter : IComparer
{
/// <summary>
/// Spécifie la colonne à trier
/// </summary>
private int ColumnToSort;
/// <summary>
/// Spécifie l'ordre de tri (en d'autres termes 'Croissant').
/// </summary>
private SortOrder OrderOfSort;
/// <summary>
/// Objet de comparaison ne respectant pas les majuscules et minuscules
/// </summary>
private CaseInsensitiveComparer ObjectCompare;
/// <summary>
/// Constructeur de classe. Initialise la colonne sur '0' et aucun tri
/// </summary>
public ListViewColumnSorter()
: this(0, SortOrder.None) { }
/// <summary>
/// Constructeur de classe. Initializes various elements
/// <param name="columnToSort">Spécifie la colonne à trier</param>
/// <param name="orderOfSort">Spécifie l'ordre de tri</param>
/// </summary>
public ListViewColumnSorter(int columnToSort, SortOrder orderOfSort)
{
// Initialise la colonne
ColumnToSort = columnToSort;
// Initialise l'ordre de tri
OrderOfSort = orderOfSort;
// Initialise l'objet CaseInsensitiveComparer
ObjectCompare = new CaseInsensitiveComparer();
// Dictionnaire de comparateurs
ColumnsComparer = new Dictionary<int, IComparer>();
ColumnsTypeComparer = new Dictionary<int, Type>();
}
/// <summary>
/// Cette méthode est héritée de l'interface IComparer. Il compare les deux objets passés en effectuant une comparaison
///qui ne tient pas compte des majuscules et des minuscules.
/// <br/>Si le comparateur n'existe pas dans ColumnsComparer, CaseInsensitiveComparer est utilisé.
/// </summary>
/// <param name="x">Premier objet à comparer</param>
/// <param name="x">Deuxième objet à comparer</param>
/// <returns>Le résultat de la comparaison. "0" si équivalent, négatif si 'x' est inférieur à 'y'
///et positif si 'x' est supérieur à 'y'</returns>
public int Compare(object x, object y)
{
int compareResult;
ListViewItem listviewX, listviewY;
// Envoit les objets à comparer aux objets ListViewItem
listviewX = (ListViewItem)x;
listviewY = (ListViewItem)y;
if (listviewX.SubItems.Count < ColumnToSort + 1 || listviewY.SubItems.Count < ColumnToSort + 1)
return 0;
IComparer objectComparer = null;
Type comparableType = null;
if (ColumnsComparer == null || !ColumnsComparer.TryGetValue(ColumnToSort, out objectComparer))
if (ColumnsTypeComparer == null || !ColumnsTypeComparer.TryGetValue(ColumnToSort, out comparableType))
objectComparer = ObjectCompare;
// Compare les deux éléments
if (comparableType != null) {
//Conversion du type
object valueX = listviewX.SubItems[ColumnToSort].Text;
object valueY = listviewY.SubItems[ColumnToSort].Text;
if (!edvTools.TryParse(ref valueX, comparableType) || !edvTools.TryParse(ref valueY, comparableType))
return 0;
compareResult = (valueX as IComparable).CompareTo(valueY);
}
else
compareResult = objectComparer.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
// Calcule la valeur correcte d'après la comparaison d'objets
if (OrderOfSort == SortOrder.Ascending) {
// Le tri croissant est sélectionné, renvoie des résultats normaux de comparaison
return compareResult;
}
else if (OrderOfSort == SortOrder.Descending) {
// Le tri décroissant est sélectionné, renvoie des résultats négatifs de comparaison
return (-compareResult);
}
else {
// Renvoie '0' pour indiquer qu'ils sont égaux
return 0;
}
}
/// <summary>
/// Obtient ou définit le numéro de la colonne à laquelle appliquer l'opération de tri (par défaut sur '0').
/// </summary>
public int SortColumn
{
set
{
ColumnToSort = value;
}
get
{
return ColumnToSort;
}
}
/// <summary>
/// Obtient ou définit l'ordre de tri à appliquer (par exemple, 'croissant' ou 'décroissant').
/// </summary>
public SortOrder Order
{
set
{
OrderOfSort = value;
}
get
{
return OrderOfSort;
}
}
/// <summary>
/// Dictionnaire de comparateurs par colonne.
/// <br/>Pendant le tri, si le comparateur n'existe pas dans ColumnsComparer, CaseInsensitiveComparer est utilisé.
/// </summary>
public Dictionary<int, IComparer> ColumnsComparer { get; set; }
/// <summary>
/// Dictionnaire de comparateurs par colonne.
/// <br/>Pendant le tri, si le comparateur n'existe pas dans ColumnsTypeComparer, CaseInsensitiveComparer est utilisé.
/// </summary>
public Dictionary<int, Type> ColumnsTypeComparer { get; set; }
}
}
Initializing a ListView :
<var>Visual.WIN.ctrlListView.OnShown</var> :
eventSender.Columns.Clear();
eventSender.SmallImageList = edvWinForm.ImageList16;
eventSender.ListViewItemSorter = new ListViewColumnSorter();
var col = eventSender.Columns.Add("Répertoire");
col.Width = 160;
col.ImageKey = "Domain";
col = eventSender.Columns.Add("Fichier");
col.Width = 180;
col.ImageKey = "File";
col = eventSender.Columns.Add("Date");
col.Width = 120;
col.ImageKey = "DateTime";
eventSender.ListViewItemSorter.ColumnsTypeComparer.Add(col.Index, DateTime);
col = eventSender.Columns.Add("Position");
col.TextAlign = HorizontalAlignment.Right;
col.Width = 80;
col.ImageKey = "Num";
eventSender.ListViewItemSorter.ColumnsTypeComparer.Add(col.Index, Int32);
Fill a ListView :
<var>Visual.WIN.cmdSearch.OnClick</var> :
//non récursif et sans fonction
..ctrlListView:Items.Clear();
..ctrlListView:Sorting = SortOrder.None;
var group = ..ctrlListView:Groups.Add(DateTime.Now.ToString()
, Path.Combine(..cboDir:Text, ..ctrlPattern1:Text) + " contenant " + ..ctrlSearch1:Text);
var perf = Environment.TickCount;
var files = new DirectoryInfo(..cboDir:Text).GetFiles(..ctrlPattern1:Text)
var search = ..ctrlSearch1:Text;
var ignoreCase = ..Search.IgnoreCase;
//var result = new StringBuilder();
var dirLength : int = ..cboDir:Text.Length;
var position : int;
var added : int = 0;
for(var i : int = 0; i < files.Length; i++){
var file = files[i];
if(search == ""
|| (position = File.ReadAllText(file.FullName).IndexOf(String(search)
, StringComparison(ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture))) > =0) {
// result.AppendLine(file.FullName.Substring(dirLength) + "\tPos : " + pkvFile.Value);
var item = ..ctrlListView:Items.Add(file.FullName.Substring(dirLength));
item.SubItems.Add(file.Name);
item.SubItems.Add(File.GetLastWriteTime(file.FullName).ToString());
item.SubItems.Add(position.ToString("# ### ##0"));
item.Group = group;
++added;
}
}
group.Header += " : " + added + "/" + files.Length + " fichier(s)"
+ " en " + (Environment.TickCount - perf).ToString("# ##0 msec");
On ListView column click :
<var>Visual.WIN.ctrlListView.OnColumnClick</var> :
// Déterminer si la colonne sélectionnée est déjà la colonne triée.
var sorter = eventSender.ListViewItemSorter;
if ( eventArgs.Column == sorter .SortColumn )
{
// Inverser le sens de tri en cours pour cette colonne.
if (sorter.Order == SortOrder.Ascending)
{
sorter.Order = SortOrder.Descending;
}
else
{
sorter.Order = SortOrder.Ascending;
}
}
else
{
// Définir le numéro de colonne à trier ; par défaut sur croissant.
sorter.SortColumn = eventArgs.Column;
sorter.Order = SortOrder.Ascending;
}
// Procéder au tri avec les nouvelles options.
eventSender.Sort();
Function edvTools.TryParse used above
class edvTools {
/// <summary>
/// Tente la conversion d'une valeur suivant un type EDVType
/// </summary>
/// <param name="pValue">Référence de la valeur à convertir</param>
/// <param name="pType">Type EDV en sortie</param>
/// <returns></returns>
public static bool TryParse(ref object pValue, System.Type pType)
{
int lIParsed;
double lDParsed;
string lsValue;
if (pValue == null) return false;
if (pType.Equals(typeof(bool))) {
bool lBParsed;
if (pValue is bool) return true;
if (double.TryParse(pValue.ToString(), out lDParsed)) {
pValue = lDParsed != 0D;
return true;
}
if (bool.TryParse(pValue.ToString(), out lBParsed)) {
pValue = lBParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(Double))) {
if (pValue is Double) return true;
if (double.TryParse(pValue.ToString(), out lDParsed)
|| double.TryParse(pValue.ToString().Replace(NumberDecimalSeparatorNOT, NumberDecimalSeparator), out lDParsed)) {
pValue = lDParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(int))) {
if (pValue is int) return true;
if (Int32.TryParse(pValue.ToString(), out lIParsed)) {
pValue = lIParsed;
return true;
}
else if (double.TryParse(pValue.ToString(), out lDParsed)) {
pValue = (int)lDParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(Byte))) {
if (pValue is byte) return true;
byte lByte;
if (Byte.TryParse(pValue.ToString(), out lByte)) {
pValue = lByte;
return true;
}
else if (double.TryParse(pValue.ToString(), out lDParsed)) {
pValue = (byte)lDParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(long))) {
long lLParsed;
if (pValue is long) return true;
if (long.TryParse(pValue.ToString(), out lLParsed)) {
pValue = lLParsed;
return true;
}
else if (double.TryParse(pValue.ToString(), out lDParsed)) {
pValue = (long)lDParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(Single))) {
if (pValue is float) return true;
Single lSParsed;
if (Single.TryParse(pValue.ToString(), out lSParsed)
|| Single.TryParse(pValue.ToString().Replace(NumberDecimalSeparatorNOT, NumberDecimalSeparator), out lSParsed)) {
pValue = lSParsed;
return true;
}
else
return false;
}
if (pType.Equals(typeof(DateTime))) {
if (pValue is DateTime) return true;
DateTime lDTParsed;
if (DateTime.TryParse(pValue.ToString(), out lDTParsed)) {
pValue = lDTParsed;
return true;
}
else if (pValue.ToString().Contains("UTC")) //Date venant de JScript
{
if (_MonthsUTC == null) InitMonthsUTC();
string[] lDateParts = pValue.ToString().Split(' ');
lDTParsed = new DateTime(int.Parse(lDateParts[5]), _MonthsUTC[lDateParts[1]], int.Parse(lDateParts[2]));
lDateParts = lDateParts[3].ToString().Split(':');
pValue = lDTParsed.AddSeconds(int.Parse(lDateParts[0]) * 3600 + int.Parse(lDateParts[1]) * 60 + int.Parse(lDateParts[2]));
return true;
}
else
return false;
}
if (pType.Equals(typeof(Array))) {
if (pValue is System.Collections.ICollection || pValue is System.Collections.ArrayList)
return true;
return pValue is System.Data.DataTable
|| pValue is string && (pValue as string).StartsWith("<");
}
if (pType.Equals(typeof(DataTable))) {
return pValue is System.Data.DataTable
|| pValue is string && (pValue as string).StartsWith("<");
}
if (pType.Equals(typeof(System.Drawing.Bitmap))) {
return pValue is System.Drawing.Image || pValue is byte[];
}
if (pType.Equals(typeof(System.Drawing.Image))) {
return pValue is System.Drawing.Image || pValue is byte[];
}
if (pType.Equals(typeof(System.Drawing.Color))) {
if (pValue is System.Drawing.Color) return true;
if (pValue is System.Drawing.KnownColor) {
pValue = System.Drawing.Color.FromKnownColor((System.Drawing.KnownColor)pValue);
return true;
}
int lARGB;
if (!int.TryParse(lsValue = pValue.ToString(), out lARGB)) {
if (lsValue.StartsWith("Color [A=", StringComparison.InvariantCulture)) {
foreach (string lsARGB in lsValue.Substring("Color [".Length, lsValue.Length - "Color []".Length).Split(','))
switch (lsARGB.TrimStart().Substring(0, 1)) {
case "A":
lARGB = int.Parse(lsARGB.Substring(2)) * 0x1000000;
break;
case "R":
lARGB += int.Parse(lsARGB.TrimStart().Substring(2)) * 0x10000;
break;
case "G":
lARGB += int.Parse(lsARGB.TrimStart().Substring(2)) * 0x100;
break;
case "B":
lARGB += int.Parse(lsARGB.TrimStart().Substring(2));
break;
default:
break;
}
pValue = System.Drawing.Color.FromArgb(lARGB);
return true;
}
if (lsValue.StartsWith("Color [", StringComparison.InvariantCulture)) {
pValue = System.Drawing.Color.FromName(lsValue.Substring("Color [".Length, lsValue.Length - "Color []".Length));
return true;
}
return false;
}
pValue = System.Drawing.Color.FromArgb(lARGB);
return true;
}
if (pType.IsEnum) {
try {
if (pValue == null) return false;
if (pValue is int || pValue is byte || pValue is ulong || pValue is long || pValue is double)
pValue = Enum.ToObject(pType, pValue);
else
pValue = Enum.Parse(pType, pValue.ToString());
}
catch {
return false;
}
}
return true;
}
}
I can see that this question was originally posted 5 yrs ago when programmers had to work harder to get their desired results. With Visual Studio 2012 and beyond, a lazy programmer can go the Design View for the Listview properties settings, and click on Properties->Sorting, choose Ascending. There are plenty of other properties features to obtain the various results a lazy (aka smart) programmer can leverage.
Late to the party, here is a short one. It has these limitations:
- It only does a plain string sort of the
SubItems
'Texts
- It uses the
ListView
'sTag
- It assumes all clicked columns will be filled
You can register & unregister any ListView
to its service; make sure the Sorting
is set to None
..:
public static class LvSort
{
static List<ListView> LVs = new List<ListView>();
public static void registerLV(ListView lv)
{
if (!LVs.Contains(lv) && lv is ListView)
{
LVs.Add(lv);
lv.ColumnClick +=Lv_ColumnClick;
}
}
public static void unRegisterLV(ListView lv)
{
if (LVs.Contains(lv) && lv is ListView)
{
LVs.Remove(lv);
lv.ColumnClick -=Lv_ColumnClick;
}
}
private static void Lv_ColumnClick(object sender, ColumnClickEventArgs e)
{
ListView lv = sender as ListView;
if (lv == null) return;
int c = e.Column;
bool asc = (lv.Tag == null) || ( lv.Tag.ToString() != c+"");
var items = lv.Items.Cast<ListViewItem>().ToList();
var sorted = asc ? items.OrderByDescending(x => x.SubItems[c].Text).ToList() :
items.OrderBy(x => x.SubItems[c].Text).ToList();
lv.Items.Clear();
lv.Items.AddRange(sorted.ToArray());
if (asc) lv.Tag = c+""; else lv.Tag = null;
}
}
To register simply do..:
public Form1()
{
InitializeComponent();
LvSort.registerLV(yourListView1);
}
Update:
Here is a slightly extended version that will let you sort all sorts of data types using any sorting rule you come up with. All you need to do is write a special string conversion for your data, add it to the function list and mark your columns. To do so simply put the column names appended with a marker string in the columns' Tags.
I have added one for sorting DataTimes and one for integers.
This version will also sort jagged ListViews, i.e. those with different numbers of subitems.
public static class LvCtl
{
static List<ListView> LVs = new List<ListView>();
delegate string StringFrom (string s);
static Dictionary<string, StringFrom> funx = new Dictionary<string, StringFrom>();
public static void registerLV(ListView lv)
{
if (!LVs.Contains(lv) && lv is ListView)
{
LVs.Add(lv);
lv.ColumnClick +=Lv_ColumnClick;
funx.Add("", stringFromString);
for (int i = 0; i < lv.Columns.Count; i++)
{
if (lv.Columns[i].Tag == null) continue;
string n = lv.Columns[i].Tag.ToString();
if (n == "") continue;
if (n.Contains("__date")) funx.Add(n, stringFromDate);
if (n.Contains("__int")) funx.Add(n, stringFromInt);
else funx.Add(n, stringFromString);
}
}
}
static string stringFromString(string s)
{
return s;
}
static string stringFromInt(string s)
{
int i = 0;
int.TryParse(s, out i);
return i.ToString("00000") ;
}
static string stringFromDate(string s)
{
DateTime dt = Convert.ToDateTime(s);
return dt.ToString("yyyy.MM.dd HH.mm.ss");
}
private static void Lv_ColumnClick(object sender, ColumnClickEventArgs e)
{
ListView lv = sender as ListView;
if (lv == null) return;
int c = e.Column;
string nt = lv.Columns[c].Tag != null ? lv.Columns[c].Tag.ToString() : "";
string n = nt.Replace("__", "§").Split('§')[0];
bool asc = (lv.Tag == null) || ( lv.Tag.ToString() != c+"");
var items = lv.Items.Cast<ListViewItem>().ToList();
var sorted = asc?
items.OrderByDescending(x => funx[nt]( c < x.SubItems.Count ?
x.SubItems[c].Text: "")).ToList() :
items.OrderBy(x => funx[nt](c < x.SubItems.Count ?
x.SubItems[c].Text : "")).ToList();
lv.Items.Clear();
lv.Items.AddRange(sorted.ToArray());
if (asc) lv.Tag = c+""; else lv.Tag = null;
}
public static void unRegisterLV(ListView lv)
{
if (LVs.Contains(lv) && lv is ListView)
{
LVs.Remove(lv);
lv.ColumnClick -=Lv_ColumnClick;
}
}
}
i would recommend you to you datagridview, for heavy stuff.. it's include a lot of auto feature listviwe does not
Use the ListView.SortExpression.
When multiple columns are sorted, this property contains a comma-separated list of the fields to sort by.
You can use a manual sorting algorithm like this
public void ListItemSorter(object sender, ColumnClickEventArgs e)
{
ListView list = (ListView)sender;
int total = list.Items.Count;
list.BeginUpdate();
ListViewItem[] items = new ListViewItem[total];
for (int i = 0; i < total; i++)
{
int count = list.Items.Count;
int minIdx = 0;
for (int j = 1; j < count; j++)
if (list.Items[j].SubItems[e.Column].Text.CompareTo(list.Items[minIdx].SubItems[e.Column].Text) < 0)
minIdx = j;
items[i] = list.Items[minIdx];
list.Items.RemoveAt(minIdx);
}
list.Items.AddRange(items);
list.EndUpdate();
}
this method uses selection sort in O^2 order and as Ascending. You can change the '>' with '<' for a descending or add an argument for this method. It sorts any column that is clicked and works perfect for small amount of data.
Since this is still a top viewed thread, I thought I might note that I came up with a dynamic solution to sort the listview by column. Here's the code just in case someone else wants to use it as well. It pretty much just involves sending the listview items to a datatable, sorting the default view of the datatable by the column name (using the index of the clicked column), and then overwriting that table with the defaultview.totable() method. Then pretty much just add them back to the listview. And wa la, its a sorted listview by column.
public void SortListView(int Index)
{
DataTable TempTable = new DataTable();
//Add column names to datatable from listview
foreach (ColumnHeader iCol in MyListView.Columns)
{
TempTable.Columns.Add(iCol.Text);
}
//Create a datarow from each listviewitem and add it to the table
foreach (ListViewItem Item in MyListView.Items)
{
DataRow iRow = TempTable.NewRow();
// the for loop dynamically copies the data one by one instead of doing irow[i] = MyListView.Subitems[1]... so on
for (int i = 0; i < MyListView.Columns.Count; i++)
{
if (i == 0)
{
iRow[i] = Item.Text;
}
else
{
iRow[i] = Item.SubItems[i].Text;
}
}
TempTable.Rows.Add(iRow);
}
string SortType = string.Empty;
//LastCol is a public int variable on the form, and LastSort is public string variable
if (LastCol == Index)
{
if (LastSort == "ASC" || LastSort == string.Empty || LastSort == null)
{
SortType = "DESC";
LastSort = "DESC";
}
else
{
SortType = "ASC";
LastSort = "ASC";
}
}
else
{
SortType = "DESC";
LastSort = "DESC";
}
LastCol = Index;
MyListView.Items.Clear();
//Sort it based on the column text clicked and the sort type (asc or desc)
TempTable.DefaultView.Sort = MyListView.Columns[Index].Text + " " + SortType;
TempTable = TempTable.DefaultView.ToTable();
//Create a listview item from the data in each row
foreach (DataRow iRow in TempTable.Rows)
{
ListViewItem Item = new ListViewItem();
List<string> SubItems = new List<string>();
for (int i = 0; i < TempTable.Columns.Count; i++)
{
if (i == 0)
{
Item.Text = iRow[i].ToString();
}
else
{
SubItems.Add(iRow[i].ToString());
}
}
Item.SubItems.AddRange(SubItems.ToArray());
MyListView.Items.Add(Item);
}
}
This method is dynamic as it uses the existing column name and doesn't require you to know the index or name of each column or even how many columns are in the listview/datatable. You can call it by doing creating an event for the listview.columnclick and then SortListView(e.column).
Based on the example pointed by RedEye, here's a class that needs less code :
it assumes that columns are always sorted in the same way, so it handles the
ColumnClick event sink internally :
public class ListViewColumnSorterExt : IComparer {
/// <summary>
/// Specifies the column to be sorted
/// </summary>
private int ColumnToSort;
/// <summary>
/// Specifies the order in which to sort (i.e. 'Ascending').
/// </summary>
private SortOrder OrderOfSort;
/// <summary>
/// Case insensitive comparer object
/// </summary>
private CaseInsensitiveComparer ObjectCompare;
private ListView listView;
/// <summary>
/// Class constructor. Initializes various elements
/// </summary>
public ListViewColumnSorterExt(ListView lv) {
listView = lv;
listView.ListViewItemSorter = this;
listView.ColumnClick += new ColumnClickEventHandler(listView_ColumnClick);
// Initialize the column to '0'
ColumnToSort = 0;
// Initialize the sort order to 'none'
OrderOfSort = SortOrder.None;
// Initialize the CaseInsensitiveComparer object
ObjectCompare = new CaseInsensitiveComparer();
}
private void listView_ColumnClick(object sender, ColumnClickEventArgs e) {
ReverseSortOrderAndSort(e.Column, (ListView)sender);
}
/// <summary>
/// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison.
/// </summary>
/// <param name="x">First object to be compared</param>
/// <param name="y">Second object to be compared</param>
/// <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
public int Compare(object x, object y) {
int compareResult;
ListViewItem listviewX, listviewY;
// Cast the objects to be compared to ListViewItem objects
listviewX = (ListViewItem)x;
listviewY = (ListViewItem)y;
// Compare the two items
compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
// Calculate correct return value based on object comparison
if (OrderOfSort == SortOrder.Ascending) {
// Ascending sort is selected, return normal result of compare operation
return compareResult;
}
else if (OrderOfSort == SortOrder.Descending) {
// Descending sort is selected, return negative result of compare operation
return (-compareResult);
}
else {
// Return '0' to indicate they are equal
return 0;
}
}
/// <summary>
/// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
/// </summary>
private int SortColumn {
set {
ColumnToSort = value;
}
get {
return ColumnToSort;
}
}
/// <summary>
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
/// </summary>
private SortOrder Order {
set {
OrderOfSort = value;
}
get {
return OrderOfSort;
}
}
private void ReverseSortOrderAndSort(int column, ListView lv) {
// Determine if clicked column is already the column that is being sorted.
if (column == this.SortColumn) {
// Reverse the current sort direction for this column.
if (this.Order == SortOrder.Ascending) {
this.Order = SortOrder.Descending;
}
else {
this.Order = SortOrder.Ascending;
}
}
else {
// Set the column number that is to be sorted; default to ascending.
this.SortColumn = column;
this.Order = SortOrder.Ascending;
}
// Perform the sort with these new sort options.
lv.Sort();
}
}
Assuming you're happy with the sort options, the class properties are private.
The only code you need to write is :
in Form declarations
private ListViewColumnSorterExt listViewColumnSorter;
in Form constructor
listViewColumnSorter = new ListViewColumnSorterExt(ListView1);
... and you're done.
And what about a single sorter that handles multiple ListViews ?
public class MultipleListViewColumnSorter {
private List<ListViewColumnSorterExt> sorters;
public MultipleListViewColumnSorter() {
sorters = new List<ListViewColumnSorterExt>();
}
public void AddListView(ListView lv) {
sorters.Add(new ListViewColumnSorterExt(lv));
}
}
in Form declarations
private MultipleListViewColumnSorter listViewSorter = new MultipleListViewColumnSorter();
in Form constructor
listViewSorter.AddListView(ListView1);
listViewSorter.AddListView(ListView2);
// ... and so on ...
I slightly modified the example from Microsoft: https://support.microsoft.com/en-us/kb/319401
This method will only sort once to sort ascending. My modifications make it sort both ways.
public class ListViewItemComparer : IComparer
{
private int col;
bool bAsc = false;
public ListViewItemComparer()
{
col = 0;
}
public ListViewItemComparer(int column, bool b)
{
col = column;
bAsc = b;
}
public int Compare(object x, object y)
{
if (bAsc)
{
return String.Compare(((ListViewItem)x).SubItems[col].Text, ((ListViewItem)y).SubItems[col].Text);
bAsc = false;
}
else
{
return String.Compare(((ListViewItem)y).SubItems[col].Text, ((ListViewItem)x).SubItems[col].Text);
bAsc = true;
}
}
}
Then I create an object of this class whenever a column header is clicked
bool sortAscending = false;
private void inventoryList_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (!sortAscending)
{
sortAscending = true;
}
else
{
sortAscending = false;
}
this.inventoryList.ListViewItemSorter = new ListViewItemComparer(e.Column, sortAscending);
}
I used the same base class as that what the others seem to use only I altered it as to allow for string, date and numerical sorting.
You can initialize it using a backing-field like so:
private readonly ListViewColumnSorterExt fileSorter;
...
public Form1()
{
InitializeComponent();
fileSorter = new ListViewColumnSorterExt(myListView);
}
Here is the code:
public class ListViewColumnSorterExt : IComparer
{
/// <summary>
/// Specifies the column to be sorted
/// </summary>
private int ColumnToSort;
/// <summary>
/// Specifies the order in which to sort (i.e. 'Ascending').
/// </summary>
private SortOrder OrderOfSort;
/// <summary>
/// Case insensitive comparer object
/// </summary>
private CaseInsensitiveComparer ObjectCompare;
private ListView listView;
/// <summary>
/// Class constructor. Initializes various elements
/// </summary>
public ListViewColumnSorterExt(ListView lv)
{
listView = lv;
listView.ListViewItemSorter = this;
listView.ColumnClick += new ColumnClickEventHandler(listView_ColumnClick);
// Initialize the column to '0'
ColumnToSort = 0;
// Initialize the sort order to 'none'
OrderOfSort = SortOrder.None;
// Initialize the CaseInsensitiveComparer object
ObjectCompare = new CaseInsensitiveComparer();
}
private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
{
ReverseSortOrderAndSort(e.Column, (ListView)sender);
}
/// <summary>
/// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison.
/// </summary>
/// <param name="x">First object to be compared</param>
/// <param name="y">Second object to be compared</param>
/// <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
public int Compare(object x, object y)
{
int compareResult;
ListViewItem listviewX, listviewY;
// Cast the objects to be compared to ListViewItem objects
listviewX = (ListViewItem)x;
listviewY = (ListViewItem)y;
if (decimal.TryParse(listviewX.SubItems[ColumnToSort].Text, out decimal dx) && decimal.TryParse(listviewY.SubItems[ColumnToSort].Text, out decimal dy))
{
//compare the 2 items as doubles
compareResult = decimal.Compare(dx, dy);
}
else if (DateTime.TryParse(listviewX.SubItems[ColumnToSort].Text, out DateTime dtx) && DateTime.TryParse(listviewY.SubItems[ColumnToSort].Text, out DateTime dty))
{
//compare the 2 items as doubles
compareResult = DateTime.Compare(dtx, dty);
}
else
{
// Compare the two items
compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
}
// Calculate correct return value based on object comparison
if (OrderOfSort == SortOrder.Ascending)
{
// Ascending sort is selected, return normal result of compare operation
return compareResult;
}
else if (OrderOfSort == SortOrder.Descending)
{
// Descending sort is selected, return negative result of compare operation
return (-compareResult);
}
else
{
// Return '0' to indicate they are equal
return 0;
}
}
/// <summary>
/// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
/// </summary>
private int SortColumn
{
set
{
ColumnToSort = value;
}
get
{
return ColumnToSort;
}
}
/// <summary>
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
/// </summary>
private SortOrder Order
{
set
{
OrderOfSort = value;
}
get
{
return OrderOfSort;
}
}
private void ReverseSortOrderAndSort(int column, ListView lv)
{
// Determine if clicked column is already the column that is being sorted.
if (column == this.SortColumn)
{
// Reverse the current sort direction for this column.
if (this.Order == SortOrder.Ascending)
{
this.Order = SortOrder.Descending;
}
else
{
this.Order = SortOrder.Ascending;
}
}
else
{
// Set the column number that is to be sorted; default to ascending.
this.SortColumn = column;
this.Order = SortOrder.Ascending;
}
// Perform the sort with these new sort options.
lv.Sort();
}
}
If you'd like to assign icons in regards to the sort order then add an image list to the Listview and make sure you update the below sample to reflect the names of your images that you use for sorting (you can assign them any name when you import them). Update the above listView_ColumnClick to something like this:
private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (sender is ListView lv)
{
ReverseSortOrderAndSort(e.Column, lv);
if ( lv.Columns[e.Column].ImageList.Images.Keys.Contains("Ascending")
&& lv.Columns[e.Column].ImageList.Images.Keys.Contains("Descending"))
{
switch (Order)
{
case SortOrder.Ascending:
lv.Columns[e.Column].ImageKey = "Ascending";
break;
case SortOrder.Descending:
lv.Columns[e.Column].ImageKey = "Descending";
break;
case SortOrder.None:
lv.Columns[e.Column].ImageKey = string.Empty;
break;
}
}
}
}
精彩评论