【C#】ListViewのソート処理、ヘッダーのマーク変更をやってみた

2018年2月17日C#,開発

おはようございます。

今回はリストビューのヘッダークリックでソートし、且つマークを表示してみます。
ファイルの一覧なので、ファイル名とサイズのソートをちょっと変えています。

プログラムは、下記の記事のものを流用します。

スポンサーリンク

プログラムの修正

新規クラスの作成

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);
        }

起動してみる

ファイル名でソート
サイズでソート

無事にソート、マークの表示ができました。

まとめ

何故、標準で出来ないのか。

謎は深まりますね。

次回は今のところ未定です。
ではでは。

スポンサーリンク


関連するコンテンツ

2018年2月17日C#,開発C#,WindowsForms,サンプルプログラム

Posted by doradora