【C#】ListViewのソート処理、ヘッダーのマーク変更をやってみた
おはようございます。
今回はリストビューのヘッダークリックでソートし、且つマークを表示してみます。
ファイルの一覧なので、ファイル名とサイズのソートをちょっと変えています。
プログラムは、下記の記事のものを流用します。
スポンサーリンク
プログラムの修正
新規クラスの作成
ListViewItemSorter.cs
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Windows.Forms; namespace WindowsFormsApp1 { public class ListViewItemSorter : IComparer { /// <summary> /// 比較する方法 /// </summary> public enum ComparerMode { /// <summary> /// 文字列として比較 /// </summary> String, /// <summary> /// 数値(Int32型)として比較 /// </summary> Integer, /// <summary> /// 日時(DataTime型)として比較 /// </summary> DateTime, /// <summary> /// ファイル名として比較 /// </summary> FileName, /// <summary> /// ファイルサイズとして比較 /// </summary> FileSize }; private ComparerMode[] _columnModes; private int _column; /// <summary> /// 並び替えるListView列の番号 /// </summary> public int Column { set { //現在と同じ列の時は、昇順降順を切り替える if (_column == value) { if (Order == SortOrder.Ascending) { Order = SortOrder.Descending; } else if (Order == SortOrder.Descending) { Order = SortOrder.Ascending; } } _column = value; } get { return _column; } } /// <summary> /// 昇順か降順か /// </summary> public SortOrder Order { set; get; } /// <summary> /// 並び替えの方法 /// </summary> public ComparerMode Mode { set; get; } /// <summary> /// 列ごとの並び替えの方法 /// </summary> public ComparerMode[] ColumnModes { set { _columnModes = value; } } /// <summary> /// コンストラクタ /// </summary> /// <param name="col">並び替える列の番号</param> /// <param name="ord">昇順か降順か</param> /// <param name="cmod">並び替えの方法</param> public ListViewItemSorter(int col, SortOrder ord, ComparerMode cmod) { Column = col; Order = ord; Mode = cmod; } public ListViewItemSorter() { Column = 0; Order = SortOrder.Ascending; Mode = ComparerMode.String; } //xがyより小さいときはマイナスの数、大きいときはプラスの数、 //同じときは0を返す public int Compare(object x, object y) { if (Order == SortOrder.None) { //並び替えない時 return 0; } int result = 0; //ListViewItemの取得 ListViewItem itemx = (ListViewItem)x; ListViewItem itemy = (ListViewItem)y; //並べ替えの方法を決定 if (_columnModes != null && _columnModes.Length > Column) { Mode = _columnModes[Column]; } //並び替えの方法別に、xとyを比較する switch (Mode) { case ComparerMode.String: //文字列をとして比較 result = string.Compare(itemx.SubItems[Column].Text, itemy.SubItems[Column].Text); break; case ComparerMode.Integer: //Int32に変換して比較 //.NET Framework 2.0からは、TryParseメソッドを使うこともできる result = int.Parse(itemx.SubItems[Column].Text).CompareTo( int.Parse(itemy.SubItems[Column].Text)); break; case ComparerMode.DateTime: //DateTimeに変換して比較 //.NET Framework 2.0からは、TryParseメソッドを使うこともできる result = DateTime.Compare( DateTime.Parse(itemx.SubItems[Column].Text), DateTime.Parse(itemy.SubItems[Column].Text)); break; case ComparerMode.FileName: result = CompareFileName(itemx.Name, itemy.Name); break; case ComparerMode.FileSize: result = CompareFileSize(itemx.SubItems[Column].Text, itemy.SubItems[Column].Text); break; } //降順の時は結果を+-逆にする if (Order == SortOrder.Descending) { result = -result; } //結果を返す return result; } /// <summary> /// ファイル名の比較をします. /// </summary> /// <param name="path1"></param> /// <param name="path2"></param> /// <returns></returns> private int CompareFileName(string path1, string path2) { FileInfo fi1 = new FileInfo(path1); FileInfo fi2 = new FileInfo(path2); if (fi1.Attributes == FileAttributes.Directory && fi2.Attributes != FileAttributes.Directory) { return -1; } else if (fi1.Attributes != FileAttributes.Directory && fi2.Attributes == FileAttributes.Directory) { return 1; } return string.Compare(path1, path2); } /// <summary> /// 単位付きのファイルサイズを比較します. /// </summary> /// <param name="size1"></param> /// <param name="size2"></param> /// <returns></returns> private int CompareFileSize(string sizeWithUnit1, string sizeWithUnit2) { string size1 = "0"; string unit1 = ""; string size2 = "0"; string unit2 = ""; List<string> units = new List<string>() { "KB","MB","GB"}; if (!string.IsNullOrEmpty(sizeWithUnit1)) { size1 = sizeWithUnit1.Split(null)[0]; unit1 = sizeWithUnit1.Split(null)[1]; } if (!string.IsNullOrEmpty(sizeWithUnit2)) { size2 = sizeWithUnit2.Split(null)[0]; unit2 = sizeWithUnit2.Split(null)[1]; } // 単位が違う場合は単位で比較 if (unit1 != unit2) { return units.IndexOf(unit1).CompareTo(units.IndexOf(unit2)); } return decimal.Parse(size1).CompareTo(decimal.Parse(size2)); } } }
グローバル変数、構造体、定数の追加
下記を追加します。
Form1.cs
private ListViewItemSorter ltvFileListSorter = null; [StructLayout(LayoutKind.Sequential)] public struct HDITEM { public Int32 mask; public Int32 cxy; [MarshalAs(UnmanagedType.LPTStr)] public String pszText; public IntPtr hbm; public Int32 cchTextMax; public Int32 fmt; public Int32 lParam; public Int32 iImage; public Int32 iOrder; }; // Parameters for ListView-Headers private const Int32 HDI_FORMAT = 0x0004; private const Int32 HDF_LEFT = 0x0000; private const Int32 HDF_STRING = 0x4000; private const Int32 HDF_SORTUP = 0x0400; private const Int32 HDF_SORTDOWN = 0x0200; private const Int32 LVM_GETHEADER = 0x1000 + 31; // LVM_FIRST + 31 private const Int32 HDM_GETITEM = 0x1200 + 11; // HDM_FIRST + 11 private const Int32 HDM_SETITEM = 0x1200 + 12; // HDM_FIRST + 12 /// <summary> /// リストビューヘッダのイメージ登録 /// </summary> /// <param name="Handle"></param> /// <param name="msg"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> [DllImport("user32.dll", EntryPoint = "SendMessage")] private static extern IntPtr SendMessageITEM(IntPtr Handle, Int32 msg, IntPtr wParam, ref HDITEM lParam);
初期化処理の修正
初期化時にソートクラスを設定
Form1.cs
// ソートクラスの設定 ltvFileListSorter = new ListViewItemSorter(0, SortOrder.Ascending, ListViewItemSorter.ComparerMode.String); ltvFileListSorter.ColumnModes = new ListViewItemSorter.ComparerMode[] { ListViewItemSorter.ComparerMode.FileName, ListViewItemSorter.ComparerMode.String, ListViewItemSorter.ComparerMode.String, ListViewItemSorter.ComparerMode.FileSize }; listView1.ListViewItemSorter = ltvFileListSorter; // ソートマークの設定 SetSortMark(listView1.Handle, 0, ltvFileListSorter.Order);
新規メソッド追加
リストのヘッダがクリックされた際のイベントを追加
Form1.cs
/// <summary> /// ファイル一覧列クリックイベントハンドラ /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) { ltvFileListSorter.Column = e.Column; listView1.Sort(); // 初期化 foreach (ColumnHeader c in listView1.Columns) { SetSortMark(listView1.Handle, c.Index, SortOrder.None); } SetSortMark(listView1.Handle, e.Column, ltvFileListSorter.Order); }
リストヘッダーのマークを設定する処理を追加
/// <summary> /// リストビューのヘッダーソートマークの制御. /// </summary> /// <param name="listViewHandle"></param> /// <param name="colIdx"></param> /// <param name="order"></param> public static void SetSortMark(IntPtr listViewHandle, int colIdx, SortOrder order) { IntPtr hColHeader = SendMessage(listViewHandle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero); HDITEM hdItem = new HDITEM(); IntPtr colHeader = new IntPtr(colIdx); hdItem.mask = HDI_FORMAT; IntPtr rtn = SendMessageITEM(hColHeader, HDM_GETITEM, colHeader, ref hdItem); if (order == SortOrder.Ascending) { hdItem.fmt &= ~HDF_SORTDOWN; hdItem.fmt |= HDF_SORTUP; } else if (order == SortOrder.Descending) { hdItem.fmt &= ~HDF_SORTUP; hdItem.fmt |= HDF_SORTDOWN; } else if (order == SortOrder.None) { hdItem.fmt &= ~HDF_SORTDOWN & ~HDF_SORTUP; } rtn = SendMessageITEM(hColHeader, HDM_SETITEM, colHeader, ref hdItem); }
起動してみる
無事にソート、マークの表示ができました。
まとめ
何故、標準で出来ないのか。
謎は深まりますね。
次回は今のところ未定です。
ではでは。
ディスカッション
コメント一覧
面白く読ませて頂きました。
記事にある、”SetSortMark”の定義はどこにあるのでしょうか。
自分ではわかりませんでした。
記事を読んでいただきありがとうございます。
“SetSortMark”の定義、投稿に漏れがありましたので追記しました。
ご指摘ありがとうございます。
今後もよろしくお願いします。
こちらこそ、良記事を上げて頂きありがとうございます。
“SetSortMark”の定義内容がわかりました。
ご対応ありがとうございます。
ただおそらく、”SendMessageITEM”の定義がないかと思います。
「グローバル変数、構造体、定数の追加」のところに、
[DllImport(“user32.dll”, EntryPoint = “SendMessage”)]
private static extern IntPtr SendMessageITEM(IntPtr Handle, Int32 msg, IntPtr wParam, ref HDITEM lParam);
を追加することで動かすことができました。
もしよろしければなのですが、
Git Hubなどを使って、プログラムを上げていただければ、
漏れがあったとしてもソースを追えばわかるので安心です。
何度も申し訳ありません。
記事を修正しましたのでご確認ください。
Git Hub の件、ご提案ありがとうございます。
近い内に導入させていただきたいと思います。
今後ともよろしくお願いします。