リストビューのアイテムをソートする

リストビューのアイテムをソートするには、CListCtrl::SortItems関数を使用する。少し変わった用法になるので、以下に説明する。


CListCtrl::SortItems関数の書式
この関数の書式は、以下のようになる。

BOOL SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData );

1番目の引数には、比較関数のアドレスを指定する。
2番目の引数には、比較関数へ渡す値を指定する(必要なければ何でもよい)。

比較関数の定義
CListCtrl::SortItems関数を使う場合、比較関数を用意しなければならない。比較関数は次のような形式になる。

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);

引数lParam1とlParam2には、比較される2つのアイテムのインデックスが渡される。
引数lParamSortには、CListCtrl::SortItems関数の2番目の引数で指定した値が渡される。

また、この関数の戻り値は以下のようになる。
 −1: lParam1のアイテムがlParam2のアイテムより前になる場合
  0: lParam1のアイテムがlParam2のアイテムと等しい場合
  1: lParam1のアイテムがlParam2のアイテムより後になる場合

注意したいのは、この関数はコールバック関数であるため、システムから呼ばれることになることである。 直接、この関数を呼ぶようなことはしない。 また比較関数は、クラスの静的(static)メンバ関数として定義するか、 あるいはどのクラスにも属さない関数として定義しなければならない。

実装の方法

ここでは、以下のようなデータがダイアログ上のリストビューに表示されているものとし、 ダイアログ上の「発売日順」ボタンを1回押すとこれを発売日の古い順(昇順)に並べ替えるものとする。 また、もう1回押すと発売日の新しい順(降順)に並べ替えるものとし、 以後押すたびに昇順、降順交互に並べ替えるものとする。
型番発売日
PC-011999.04.13
PC-01X1999.11.15
PC-021999.09.10

まず、必要な関数のヘッダを用意する。リストビューコントロールを貼り付けたダイアログのヘッダファイルに、 以下の処理を記述する。比較関数は、ここではダイアログクラスのstaticメンバ関数として定義する。

	// ヘッダファイルでの記述 --------------------
	class CMyDlg : public CDialog
	{
	// 構築
	public:
		static int CALLBACK CompareFunc(LPARAM param1, LPARAM param2, LPARAM param3);
		void RenumberItem();
		……
	};
次に比較関数の実装を行う。先に説明したように、この関数は1番目と2番目の引数を比較し、 その結果を-1、0、1のいずれかの値で返すという仕様になっているので、当然そのように実装する必要がある。
	// ソースファイルでの記述 --------------------
	int CALLBACK CMyDlg::CompareFunc(LPARAM param1, LPARAM param2, LPARAM param3)
	{
		// staticメンバ関数なので、GetParent()で親ウィンドウを取得することはできない
		CMyDlg* pDlg = (CMyDlg*)AfxGetMainWnd();

		// 比較される2つのアイテムから「発売日」の文字列を取得する
		CString str1 = pDlg->m_list1.GetItemText(param1, 1);
		CString str2 = pDlg->m_list1.GetItemText(param2, 1);

		// strcmpを使うなら、降順の場合はstr1とstr2を逆にしなければならない
		int iReturn;
		if( !param3 )
			iReturn = strcmp( str1, str2 );		// 昇順
		else
			iReturn = strcmp( str2, str1 );		// 降順

		return iReturn;
	}
比較関数の定義のところを見ればわかるのだが、アイテムを入れ替える場合には比較関数で1を返すようにしなければならない。 strcmp関数を使う場合は、この関数の第1引数の方が大きい場合に1を返すよう注意すること。
上の例では、param3の値が0のときは昇順、そうでない場合は降順に並べ替えるようにしてある。

比較関数ができたので、あとはSortItems関数でこれを呼べばよいのだが、実はそれだけでは正しくソートできない。 SortItems関数を呼ぶ前には、必ずアイテムに対しそれが並んでいる順番に番号をつけていかなければならないのである。 この処理は、ヘッダの部分で定義したRenumberItem関数がその役目を果たす。以下のように実装する。

	// ソースファイルでの記述 --------------------
	void CMyDlg::RenumberItem()
	{
		LV_ITEM lvItem;

		// m_list1はCListCtrl型のDDX変数で、ソートを行うリストビューコントロールのオブジェクトであるものとする
		for( int i = 0; i < m_list1.GetItemCount(); i++ ) {
			lvItem.iItem = i;
			lvItem.iSubItem = 0;
			lvItem.mask = LVIF_PARAM;
			lvItem.lParam = i;		// ここで番号をアイテムに指定する
			m_list1.SetItem(&lvItem);
		}
	}
ここまでできたら、あとは「発売日」ボタンが押されたときの処理を実装すればよい。 この場合ハンドラ関数の実装を以下に記述する。
	// ソースファイルでの記述 --------------------
	void CMyDlg::OnButton1() 
	{
		// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください

		RenumberItem();				// SortItems関数を呼び出す前には必ず実行

		static BOOL bSort = FALSE;			// staticで定義すること
		m_list1.SortItems(CompareFunc, bSort);
		bSort = !bSort;				// bSortがFALSEならTRUE、TRUEならFALSEにする
	}
SortItems関数の第2引数に指定したbSortの値は、比較関数CompareFuncの3番目の引数param3に渡される。 つまり、1回目はbSortの値がFALSEなのでparam3もFALSE(すなわち0)となり、昇順ソートが行われる。 また2回目はbSortがTRUEになっているのでparam3もTRUE(非0)となり、降順でソートされるというわけである。

目次へ