今日MFCのCListCtrlのソートでハマってしまったので、備忘録として残す。
下図のようにカラムが一つしか登録されていない場合に、上手くソートできない場合があった。
このときにはCompareFunc()に渡されるlParam1及びlParam2の整合が取れなくなり正しくソートできない。
回避策として幅0のダミーカラムを登録することで正常に動作したが、本来の処理をご存知の方はご教示いただきたい。
ここからは、CListCtrlを使用したソートを調べていて思ったこと。
以下にコード例を示す。
#エラー処理は省いているので注意
/*static*/ int CALLBACK CListViewTestDlg::CompareFunc( LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort ) { // staticでない比較関数に飛ばす CListViewTestDlg* dlg = (CListViewTestDlg*)lParamSort; return dlg->CompareFunc( lParam1, lParam2 ); } int CListViewTestDlg::CompareFunc( LPARAM lParam1, LPARAM lParam2 ) { CString caption[2]; LPARAM param[2] = { lParam1, lParam2 }; // パラメータからインデックスを取得 for ( int i = 0; i < 2; ++i ) { LVFINDINFO fi = { 0 }; fi.flags = LVFI_PARAM; fi.lParam = param[i]; caption[i] = m_list.GetItemText( m_list.FindItem( &fi ), 0 ); } // 比較 int ret = 0; if ( up_ ) { ret = caption[0].CompareNoCase( caption[1] ); } else { ret = caption[1].CompareNoCase( caption[0] ); } return ret; } void CListViewTestDlg::OnColumnclickList(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; // 各インデックスにパラメータを設定 for( int i = 0; i < m_list.GetItemCount(); i++ ) { m_list.SetItemData( i, i ); } // ソート m_list.SortItems( &CListViewTestDlg::CompareFunc, (LPARAM)this ); up_ = !up_; *pResult = 0; }
詳細は各所で説明されているので割愛するが一点だけ気になる点が。
調べた中では、比較関数に渡されるlParam1、lParam2を直接GetItemText()の第一引数に入れて文字列を取ってきている例
caption1 = m_list.GetItemText( lParam1, 0 );
が大半だが、Visual Studio 2008付属のMSDNドキュメントを見るとこのように記述されている。
パラメータ lParam1 は、比較される最初のアイテムに関連付けられている 32 ビットの値です。パラメータ lParam2 は、2 番目のアイテムに関連付けられている値です。これらの値は、リストに挿入するときに、アイテムの LVITEM 構造体の lParam メンバで指定します。
lParam1、lParam2はインデックスではなくLVITEM構造体のlParam。すなわちm_list.SetItemData( i, i );の第2引数に設定した値が設定される(内部的にはLVIF_PARAMフラグとともにLVITEMのlParamに設定される)。
試しに
m_list.SetItemData( i, i + 10 )
としてみた時にインデックス0,1のlParam1、lParam2は10,11が設定される。
このような理由から、GetItemText()のためのインデックスを取得するには
LVFINDINFO fi = { 0 }; fi.flags = LVFI_PARAM; fi.lParam = lParam1; caption1 = m_list.GetItemText( m_list.FindItem( &fi ), 0 );
とする必要があるのではないか?