CodeZine(コードジン)

特集ページ一覧

ListViewコントロールに柔軟なソート機能を追加する

ListViewクラスを拡張したSortableListViewクラスの作成

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/05/25 08:00

ダウンロード サンプルソース (57.9 KB)

ListViewコントロールで、ユーザーが列ヘッダをクリックしたときに、その列でソートしたり、すべての列でソートしたり、あるいは考えられる他のやり方でソートを行う方法を習得します。

目次

はじめに

 私はListViewコントロールをよく使います。といっても、大小のアイコンビューを表示するためではなく、もっぱらWindows Explorer画面の右側に表示されるような詳細ビューを表示するために使っています。詳細ビューとは、Windows Explorerの[表示]メニューで[詳細]を選択するか、ツールバーのドロップダウンボタンで[詳細]を選択したときに表示されるビューです。

 詳細ビューとして表示した場合、ListViewは簡単に使える読み取り専用グリッドのような動作をします。ListViewコントロールを使用すると、開発者とユーザーの双方にメリットがあります。開発者にとってのメリットは、アイテムおよびサブアイテムの追加、削除、再配置が簡単に行えることです。ユーザーにとってのメリットは、行の選択、列の再配置、さらに行のソートまで行えることです。

 ListViewにはこうした操作のための基本的な手段が用意されていますが、うまく動かすためにはある程度のコードを追加する必要があります。本稿で説明するSortableListViewコントロールでも、便利な機能を実現するためにコードを書き加えています。SortableListViewはListViewクラスを継承しており、ListViewコントロールのすべての機能に加え、全列によるソートや選択した列によるソートの機能にも対応しています。

選択した列によるソート処理

 ListViewコントロールには、表示するアイテムを保持するItemsコレクションが含まれています。このコレクション内の各アイテムは、ListViewItemクラスのインスタンスであり、アイテムのもっと詳細な情報(サブアイテム)を取り扱うSubItemsコレクションを公開しています。詳細ビューでは、一番左の列にアイテムを表すテキストが、その他の列にそのサブアイテムがそれぞれListViewによって一覧表示されます。

 ListViewコントロールには、自らのデータをソートするSortメソッドが最初から用意されています。しかし、デフォルトでは、このコントロールによるソートの対象はアイテムだけであり、サブアイテムはソートできません。幸い、ListViewコントロールのListViewItemSorterプロパティを使えば、このコントロールによるアイテムのソート方法を変更できます。

 ソート処理をカスタマイズするには、ListViewItemSorterプロパティに対して、IComparerインターフェイスを実装したオブジェクトを指定しなければなりません。このインターフェイスで定義されているのは、Compareメソッドだけです。このメソッドは、2つのアイテムをパラメータとして受け取り、両アイテムのソート順の比較結果によって、-1(最初のアイテムが2番目のアイテムより小さい)、0(両アイテムのソート順が同じ)、1(最初のアイテムが2番目より大きい)のいずれかを返します。

 今回作成するSortableListViewコントロールでは、IComparerクラスを使って2つの新しいソート機能を実現します。1つ目の機能では、最初の列に限らず、任意の列を選択してソートできるようにします。図1に、Pages列で昇順(値が最小の要素が一番上になる)にソートしたListViewコントロールの様子を示します。Pages列にデータを持たない行が一番上にソートされることに注意してください。ListViewコントロールによって、現在のソートの基準となっている列(Pages列)に上向きの矢印が表示されています。これは、その列が昇順にソートされていることを示しています(矢印は最小のアイテムの方向を指します)。列ヘッダをクリックすると、降順のソート処理に切り換わります。

図1 ページ数によるソート結果。SortableListViewコントロールのデータがPages列で昇順にソートされている。
図1 ページ数によるソート結果。SortableListViewコントロールのデータがPages列で昇順にソートされている。

 別の列ヘッダをクリックすると、Windows Explorerと同じように、その列を基準としてソートが行われます。図2に、Title列を2回クリックした後の同じフォームの状態を示します。1回目のクリックでこの列によるソートが実行され、2回目のクリックで降順のソートに変わっています。

図2 タイトルによるソート結果。同じくSortableListViewコントロールにより、Title列で降順にソートされている。
図2 タイトルによるソート結果。同じくSortableListViewコントロールにより、Title列で降順にソートされている。

 以下に、SortableListViewコントロールの中でこの機能を実現するために使っているSelectedColumnSorterクラスのコードを示します。なお、本稿のダウンロードファイルにはVisual Basic版とC#版の両方が収録されています。

' Sort the ListView items by the selected column.
Private Class SelectedColumnSorter
   Implements IComparer

   ' Compare two ListViewItems.
   Public Function Compare(ByVal x As Object, _
     ByVal y As Object) As Integer _
     Implements System.Collections.IComparer.Compare
      ' Get the items.
      Dim itemx As ListViewItem = DirectCast(x, ListViewItem)
      Dim itemy As ListViewItem = DirectCast(y, ListViewItem)

      ' Get the selected column index.
      Dim slvw As SortableListView = itemx.ListView
      Dim idx As Integer = slvw.m_SelectedColumn
      If idx < 0 Then Return 0

      ' Compare the sub-items.
      If itemx.ListView.Sorting = SortOrder.Ascending Then
         Return String.Compare( _
           ItemString(itemx, idx), ItemString(itemy, idx))
      Else
         Return -String.Compare( _
           ItemString(itemx, idx), ItemString(itemy, idx))
      End If
   End Function

   ' Return a string representing this item's sub-item.
   Private Function ItemString( _
     ByVal listview_item As ListViewItem, ByVal idx As Integer) _
     As String
      Dim slvw As SortableListView = listview_item.ListView

      ' Make sure the item has the needed sub-item.
      Dim value As String = ""
      If idx <= listview_item.SubItems.Count - 1 Then
         value = listview_item.SubItems(idx).Text
      End If

      ' Return the sub-item's value.
      If slvw.Columns(idx).TextAlign = _
         HorizontalAlignment.Right _
      Then
         ' Pad so numeric values sort properly.
         Return value.PadLeft(20)
      Else
         Return value
      End If
   End Function
End Class

 Compare関数は、2つのパラメータをジェネリックなObjectからListViewItemオブジェクトに変換します。また、最初のアイテムのListViewプロパティを使って、そのアイテムを含むSortableListViewコントロールを取得しています。

 続いて、SortableListViewコントロールのm_SelectedColumn変数を参照して、ソートに使用すべき列を決めます(この変数については後で説明します)。どの列も選択されていない場合、この関数はそれ以上何も行わずに処理を終了します。

 次に、SortableListViewコントロールのSortingプロパティを参照して、オブジェクトのソートを昇順で行うか降順で行うかを決めます。SelectedColumnSorterオブジェクトは自らのItemString関数を呼び出し、ListViewItemオブジェクトのそれぞれについて、選択した列のアイテムを表す文字列を生成します。さらに、これらの各文字列をString.Compareを使って比較し、その結果を返します。アイテムのソートが降順で行われる場合は、適切な結果が得られるようにString.Compareの結果の符号を反転させます。

 ItemString関数は、あるListViewItemで選択されている列を表す文字列を返します。まず、そのListViewItemに目的の列があるかどうかを確認します。例えば、ListViewItemに1つしかサブアイテムがないのに5番目の列でソートしようとした場合、その列については空の文字列が使われます。

 対象列の適切な値(サブアイテムのテキスト文字列または空文字列)が得られると、対応する列のTextAlignプロパティをチェックします。その列のテキストが右詰めであれば、数値データである可能性が高いため、単純にアルファベット順でソートを行ってはなりません。例えば、アルファベット順では文字列"100"が"11"よりも前になってしまいますが、ソート後のリストではおそらく"11"のほうを先に表示したいはずです。

 右詰めの数字を適切にソートするために、ItemString関数はそうした値の左側に空白を追加します。空白は、アルファベット順で数字よりも前になるので、文字列"11"はねらい通りに"100"よりも前に来ることになります。

著者による注釈
 データによっては、こうしたチェックをもっと念入りに行う必要があるかもしれません。例えば、このコードでは、右詰めの列にはせいぜい20桁の単純な数値しか含まれていないと仮定しています。つまり、このコードでは、"1e10"が"1E+10"と同じであることや、"April 1"が"January 1"よりも後になることは判断できないのです。ただし、一般的な考え方を示すサンプルコードにはなっています。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

著者プロフィール

  • Rod Stephens(Rod Stephens)

    10冊以上の書籍と200点以上の雑誌記事の著者にしてコンサルタント。著作の大部分はVisual Basicに関するものである。これまで、修復ディスパッチ、燃料税トラッキング、プロフェッショナルなフットボールトレーニング、廃水処理、地図作成、チケット販売などの種々雑多なアプリケーションに従事してきた。...

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

あなたにオススメ

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5