DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

ListViewの項目を並び替える

ListViewの項目が自動的に並び替えられるようにするには、ListView.SortingプロパティをSortOrder.Ascending(昇順)またはSortOrder.Descending(降順)にします。ただしこの方法では、サブアイテムを比較しての並び替えができず、数字、時間の並び替えも正しく行われません。

これらの問題を解決するためには、ListView.ListViewItemSorterプロパティに、並べ方を定義したIComparerインターフェイスを実装したクラスのインスタンスを設定します。なお、IComparerによる並び替えについては、「コレクション(または配列)内の要素を方法を指定して並び替える(または検索する)」でも説明しています。

まずはごく簡単な例を示します。ここではListViewコントロール(ListView1)で列ヘッダがクリックされた時に、その列の文字列を昇順に並べ替えるようにします。そのために、まずは次のようなクラスを作成します。

VB.NET
コードを隠すコードを選択
Imports System.Collections 
Imports System.Windows.Forms 

''' <summary>
''' ListViewの項目の並び替えに使用するクラス
''' </summary>
Public Class ListViewItemComparer
    Implements IComparer
    Private _column As Integer

    ''' <summary>
    ''' ListViewItemComparerクラスのコンストラクタ
    ''' </summary>
    ''' <param name="col">並び替える列番号</param>
    Public Sub New(ByVal col As Integer)
        _column = col
    End Sub

    'xがyより小さいときはマイナスの数、大きいときはプラスの数、
    '同じときは0を返す
    Public Function Compare(ByVal x As Object, ByVal y As Object) _
            As Integer Implements System.Collections.IComparer.Compare
        'ListViewItemの取得
        Dim itemx As ListViewItem = CType(x, ListViewItem)
        Dim itemy As ListViewItem = CType(y, ListViewItem)

        'xとyを文字列として比較する
        Return String.Compare(itemx.SubItems(_column).Text, _
            itemy.SubItems(_column).Text)
    End Function
End Class
C#
コードを隠すコードを選択
using System.Collections;
using System.Windows.Forms;

/// <summary>
/// ListViewの項目の並び替えに使用するクラス
/// </summary>
public class ListViewItemComparer : IComparer
{
    private int _column;

    /// <summary>
    /// ListViewItemComparerクラスのコンストラクタ
    /// </summary>
    /// <param name="col">並び替える列番号</param>
    public ListViewItemComparer(int col)
    {
        _column = col;
    }

    //xがyより小さいときはマイナスの数、大きいときはプラスの数、
    //同じときは0を返す
    public int Compare(object x, object y)
    {
        //ListViewItemの取得
        ListViewItem itemx = (ListViewItem) x;
        ListViewItem itemy = (ListViewItem) y;

        //xとyを文字列として比較する
        return string.Compare(itemx.SubItems[_column].Text,
            itemy.SubItems[_column].Text);
    }
}

このクラスを使って並べ替えを行うには、ListView.ColumnClickイベントハンドラでListViewItemSorterを設定します。次のコードはListViewのあるフォームクラス内に記述してください。これで列ヘッダをクリックすると、並び替えられるようになったはずです。

VB.NET
コードを隠すコードを選択
'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As EventArgs) Handles MyBase.Load
    '詳細表示にする
    ListView1.View = View.Details
    'ColumnClickイベントハンドラの追加
    AddHandler ListView1.ColumnClick, _
        AddressOf ListView1_ColumnClick

    'ListViewItemを追加する
    ListView1.Items.Add( _
        New ListViewItem(New String() {"おはよう", "80"}))
    ListView1.Items.Add( _
        New ListViewItem(New String() {"こんにちは", "5"}))
    ListView1.Items.Add( _
        New ListViewItem(New String() {"おやすみ", "100"}))

    '列の追加
    ListView1.Columns.Add("文字列", 100, HorizontalAlignment.Left)
    ListView1.Columns.Add("数字", 100, HorizontalAlignment.Right)
End Sub

'列がクリックされた時
Private Sub ListView1_ColumnClick(ByVal sender As Object, _
        ByVal e As ColumnClickEventArgs)
    'ListViewItemSorterを指定する
    ListView1.ListViewItemSorter = _
        New ListViewItemComparer(e.Column)
    '並び替える(ListViewItemSorterを設定するとSortが自動的に呼び出される)
    'ListView1.Sort()
End Sub
C#
コードを隠すコードを選択
//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //詳細表示にする
    ListView1.View = View.Details;
    //ColumnClickイベントハンドラの追加
    ListView1.ColumnClick += 
        new ColumnClickEventHandler(ListView1_ColumnClick);

    //ListViewItemを追加する
    ListView1.Items.Add(
        new ListViewItem(new string[] {"おはよう", "80"}));
    ListView1.Items.Add(
        new ListViewItem(new string[] {"こんにちは", "5"}));
    ListView1.Items.Add(
        new ListViewItem(new string[] {"おやすみ", "100"}));

    //列の追加
    ListView1.Columns.Add(
        "文字列", 100, HorizontalAlignment.Left);
    ListView1.Columns.Add(
        "数字", 100, HorizontalAlignment.Right);
}

//列がクリックされた時
private void ListView1_ColumnClick(
    object sender, ColumnClickEventArgs e)
{
    //ListViewItemSorterを指定する
    ListView1.ListViewItemSorter =
        new ListViewItemComparer(e.Column);
    //並び替える(ListViewItemSorterを設定するとSortが自動的に呼び出される)
    //ListView1.Sort();
    
}

さらに拡張

せっかくなので、上記のListViewItemComparerクラスをさらに拡張して、より多機能にしてみましょう。

まず、昇順、降順を指定できるようにします。さらに、文字列として並び替えるか、それとも、数字や日時として並び替えるかという「並び替え方」を変更できるようにしましょう。

昇順、降順をいちいち指定するのは面倒なので、並び替える列を指定するColumnプロパティで前と同じ列が指定された時は、トグルで昇順、降順が換わるようにします。また、あらかじめ列ごとに並べ替え方を指定できるようにしておきます。

これらの改良を加えたListViewItemComparerクラスは次のようなものです。

VB.NET
コードを隠すコードを選択
Imports System 
Imports System.Collections 
Imports System.Windows.Forms 

''' <summary>
''' ListViewの項目の並び替えに使用するクラス
''' </summary>
Public Class ListViewItemComparer
    Implements IComparer

    ''' <summary>
    ''' 比較する方法
    ''' </summary>
    Public Enum ComparerMode
        ''' <summary>
        ''' 文字列として比較
        ''' </summary>
        [String]
        ''' <summary>
        ''' 数値(Int32型)として比較
        ''' </summary>
        [Integer]
        ''' <summary>
        ''' 日時(DataTime型)として比較
        ''' </summary>
        DateTime
    End Enum

    Private _column As Integer
    Private _order As SortOrder
    Private _mode As ComparerMode
    Private _columnModes As ComparerMode()

    ''' <summary>
    ''' 並び替えるListView列の番号
    ''' </summary>
    Public Property Column() As Integer
        '現在と同じ列の時は、昇順降順を切り替える
        Get
            Return _column
        End Get
        Set(value As Integer)
            If _column = value Then
                If _order = SortOrder.Ascending Then
                    _order = SortOrder.Descending
                ElseIf _order = SortOrder.Descending Then
                    _order = SortOrder.Ascending
                End If
            End If
            _column = value
        End Set
    End Property

    ''' <summary>
    ''' 昇順か降順か
    ''' </summary>
    Public Property Order() As SortOrder
        Get
            Return _order
        End Get
        Set(value As SortOrder)
            _order = value
        End Set
    End Property

    ''' <summary>
    ''' 並び替えの方法
    ''' </summary>
    Public Property Mode() As ComparerMode
        Get
            Return _mode
        End Get
        Set(value As ComparerMode)
            _mode = value
        End Set
    End Property

    ''' <summary>
    ''' 列ごとの並び替えの方法
    ''' </summary>
    Public WriteOnly Property ColumnModes() As ComparerMode()
        Set(value As ComparerMode())
            _columnModes = value
        End Set
    End Property

    ''' <summary>
    ''' ListViewItemComparerクラスのコンストラクタ
    ''' </summary>
    ''' <param name="col">並び替える列の番号</param>
    ''' <param name="ord">昇順か降順か</param>
    ''' <param name="cmod">並び替えの方法</param>
    Public Sub New(col As Integer, ord As SortOrder, cmod As ComparerMode)
        _column = col
        _order = ord
        _mode = cmod
    End Sub

    Public Sub New()
        _column = 0
        _order = SortOrder.Ascending
        _mode = ComparerMode.[String]
    End Sub

    'xがyより小さいときはマイナスの数、大きいときはプラスの数、
    '同じときは0を返す
    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _
        Implements IComparer.Compare

        If _order = SortOrder.None Then
            '並び替えない時
            Return 0
        End If

        Dim result As Integer = 0
        'ListViewItemの取得
        Dim itemx As ListViewItem = DirectCast(x, ListViewItem)
        Dim itemy As ListViewItem = DirectCast(y, ListViewItem)

        '並べ替えの方法を決定
        If Not (_columnModes Is Nothing) AndAlso _
                _columnModes.Length > _column Then
            _mode = _columnModes(_column)
        End If

        '並び替えの方法別に、xとyを比較する
        Select Case _mode
            Case ComparerMode.[String]
                '文字列として比較
                result = String.Compare( _
                    itemx.SubItems(_column).Text, _
                    itemy.SubItems(_column).Text)
                Exit Select
            Case ComparerMode.[Integer]
                'Int32に変換して比較
                '.NET Framework 2.0からは、TryParseメソッドを使うこともできる
                result = _
                    Integer.Parse(itemx.SubItems(_column).Text).CompareTo( _
                    Integer.Parse(itemy.SubItems(_column).Text))
                Exit Select
            Case ComparerMode.DateTime
                'DateTimeに変換して比較
                '.NET Framework 2.0からは、TryParseメソッドを使うこともできる
                result = DateTime.Compare( _
                    DateTime.Parse(itemx.SubItems(_column).Text), _
                    DateTime.Parse(itemy.SubItems(_column).Text))
                Exit Select
        End Select

        '降順の時は結果を+-逆にする
        If _order = SortOrder.Descending Then
            result = -result
        End If

        '結果を返す
        Return result
    End Function
End Class
C#
コードを隠すコードを選択
using System;
using System.Collections;
using System.Windows.Forms;

/// <summary>
/// ListViewの項目の並び替えに使用するクラス
/// </summary>
public class ListViewItemComparer : IComparer
{
    /// <summary>
    /// 比較する方法
    /// </summary>
    public enum ComparerMode
    {
        /// <summary>
        /// 文字列として比較
        /// </summary>
        String,
        /// <summary>
        /// 数値(Int32型)として比較
        /// </summary>
        Integer,
        /// <summary>
        /// 日時(DataTime型)として比較
        /// </summary>
        DateTime
    };

    private int _column;
    private SortOrder _order;
    private ComparerMode _mode;
    private ComparerMode[] _columnModes;

    /// <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
        {
            _order = value;
        }
        get
        {
            return _order;
        }
    }
    /// <summary>
    /// 並び替えの方法
    /// </summary>
    public ComparerMode Mode
    {
        set
        {
            _mode = value;
        }
        get
        {
            return _mode;
        }
    }
    /// <summary>
    /// 列ごとの並び替えの方法
    /// </summary>
    public ComparerMode[] ColumnModes
    {
        set
        {
            _columnModes = value;
        }
    }

    /// <summary>
    /// ListViewItemComparerクラスのコンストラクタ
    /// </summary>
    /// <param name="col">並び替える列の番号</param>
    /// <param name="ord">昇順か降順か</param>
    /// <param name="cmod">並び替えの方法</param>
    public ListViewItemComparer(
        int col, SortOrder ord, ComparerMode cmod)
    {
        _column = col;
        _order = ord;
        _mode = cmod;
    }
    public ListViewItemComparer()
    {
        _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;
        }

        //降順の時は結果を+-逆にする
        if (_order == SortOrder.Descending)
        {
            result = -result;
        }

        //結果を返す
        return result;
    }
}

このクラスの使用法は例えば次のようになります。

VB.NET
コードを隠すコードを選択
'ListViewItemSorterに指定するフィールド
Dim listViewItemSorter As ListViewItemComparer

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As EventArgs) Handles MyBase.Load
    '詳細表示にする
    ListView1.View = View.Details
    'ColumnClickイベントハンドラの追加
    AddHandler ListView1.ColumnClick, _
        AddressOf ListView1_ColumnClick

    'ListViewItemを追加する
    ListView1.Items.Add( _
        New ListViewItem(New String() {"おはよう", "80"}))
    ListView1.Items.Add( _
        New ListViewItem(New String() {"こんにちは", "5"}))
    ListView1.Items.Add( _
        New ListViewItem(New String() {"おやすみ", "100"}))

    '列の追加
    ListView1.Columns.Add("文字列", 100, HorizontalAlignment.Left)
    ListView1.Columns.Add("数字", 100, HorizontalAlignment.Right)

    'ListViewItemComparerの作成と設定
    listViewItemSorter = New ListViewItemComparer
    listViewItemSorter.ColumnModes = _
        New ListViewItemComparer.ComparerMode() _
        {ListViewItemComparer.ComparerMode.String, _
        ListViewItemComparer.ComparerMode.Integer}
    'ListViewItemSorterを指定する
    ListView1.ListViewItemSorter = listViewItemSorter
End Sub

'列がクリックされた時
Private Sub ListView1_ColumnClick(ByVal sender As Object, _
        ByVal e As ColumnClickEventArgs)
    'クリックされた列を設定
    listViewItemSorter.Column = e.Column
    '並び替える
    ListView1.Sort()
End Sub
C#
コードを隠すコードを選択
//ListViewItemSorterに指定するフィールド
ListViewItemComparer listViewItemSorter;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //詳細表示にする
    ListView1.View = View.Details;
    //ColumnClickイベントハンドラの追加
    ListView1.ColumnClick +=
        new ColumnClickEventHandler(ListView1_ColumnClick);

    //ListViewItemを追加する
    ListView1.Items.Add(
        new ListViewItem(new string[] {"おはよう", "80"}));
    ListView1.Items.Add(
        new ListViewItem(new string[] {"こんにちは", "5"}));
    ListView1.Items.Add(
        new ListViewItem(new string[] {"おやすみ", "100"}));

    //列の追加
    ListView1.Columns.Add("文字列", 100, HorizontalAlignment.Left);
    ListView1.Columns.Add("数字", 100, HorizontalAlignment.Right);

    //ListViewItemComparerの作成と設定
    listViewItemSorter = new ListViewItemComparer();
    listViewItemSorter.ColumnModes =
        new ListViewItemComparer.ComparerMode[]
    {
        ListViewItemComparer.ComparerMode.String,
        ListViewItemComparer.ComparerMode.Integer
    };
    //ListViewItemSorterを指定する
    ListView1.ListViewItemSorter = listViewItemSorter;
}

//列がクリックされた時
private void ListView1_ColumnClick(
    object sender, ColumnClickEventArgs e)
{
    //クリックされた列を設定
    listViewItemSorter.Column = e.Column;
    //並び替える
    ListView1.Sort();
}
  • 履歴:
  • 2012/2/7 ListViewItemComparerコンストラクタのパラメータのドキュメントコメントが間違えていたのを修正。
  • 2013/10/15 VB.NETの「And」を「AndAlso」に修正。
  • 2013/11/11 ListViewItemComparerで数値を比較する方法を変更。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。