Windowsプログラミング(3) リストビューのソート

#include <windows.h>
#include <commctrl.h>
#include <string.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
int CALLBACK listcmp(LPARAM lp1, LPARAM lp2, LPARAM lp3);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int nShowCmd){
(省略)
}

LPTSTR key[] = {"key1", "key2", "key3"};
LPTSTR val[] = {"val1", "val2", "val3"};
int order[3] = {1,1,1}; // 0:昇順 1:降順
HWND hList;

LRESULT CALLBACK WndProc (HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
     LV_COLUMN col;
     LV_ITEM item;
     int i, subitem;

     switch(nMsg){
     case WM_CREATE:
          InitCommonControls();
          hList = CreateWindowEx(
               0,                 /* 拡張ウインドウスタイル */
               WC_LISTVIEW, NULL, /* クラス名 タイトル */
               WS_CHILD | WS_VISIBLE | LVS_REPORT,  /* ウインドウスタイル */
               0, 0, 0, 0,                          /* 位置、サイズ */
               hWnd,                                /* 親ウインドウ */
               (HMENU)1,                            /* メニューハンドル */
               ((LPCREATESTRUCT)lParam)->hInstance, /* インスタンス */
               NULL);               /* WM_CREATE時に渡される引数 */

          col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
          col.fmt  = LVCFMT_LEFT;
          
          col.cx = 100;
          col.pszText  = "key";
          col.iSubItem = 0;
          ListView_InsertColumn(hList, 0, &col);

          col.cx = 200;
          col.pszText = "value";
          col.iSubItem = 1;
          ListView_InsertColumn(hList, 1, &col);
          
          for(i=0; i<3; i++){
               item.mask = LVIF_TEXT | LVIF_PARAM;
               item.pszText = key[i%3];
               item.iItem = i;
               item.iSubItem = 0;
               item.lParam = i;
               ListView_InsertItem(hList, &item);

               item.mask = LVIF_TEXT;
               item.pszText = val[i%3];
               item.iItem = i;
               item.iSubItem = 1;
               ListView_SetItem(hList, &item);
          }
          break;

     case WM_NOTIFY:
          if (((LPNMHDR)lParam)->hwndFrom == hList){
               if(((LV_DISPINFO *)lParam)->hdr.code == LVN_COLUMNCLICK){
                    subitem = ((NM_LISTVIEW *)lParam)->iSubItem;
                    order[subitem] = 1 - order[subitem];
                    ListView_SortItems(hList, listcmp, subitem);
               }
          }
          break;
     case WM_SIZE:
          MoveWindow(hList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
          break;
     case WM_DESTROY:
          PostQuitMessage(0);
          break;
     default:
          return (DefWindowProc(hWnd, nMsg, wParam, lParam));
     }
     return 0;
}

int CALLBACK listcmp(LPARAM lp1, LPARAM lp2, LPARAM lp3)
{
     LV_FINDINFO lvf;
     int nItem1, nItem2;
     char buf1[256], buf2[256];

     lvf.flags = LVFI_PARAM;
     lvf.lParam = lp1;
     nItem1 = ListView_FindItem(hList, -1, &lvf);

     lvf.lParam = lp2;
     nItem2 = ListView_FindItem(hList, -1, &lvf);
     ListView_GetItemText(hList, nItem1, (int)lp3, buf1, 256);
     ListView_GetItemText(hList, nItem2, (int)lp3, buf2, 256);

     return(order[(int)lp3]==0) ? strcmp(buf1, buf2) : strcmp(buf2, buf1);
}

大体一緒だけど、項目名をマウスでクリックするとWM_NOTIFY、LVN_COLUMNCLICKというイベントが渡されるのでそれをキャッチすればいいらしい。lParamのキャストされっぷりがすごいなと思うわけですが、データサイズがおなじで型名だけが違うのかなあ・・・。なんかめちゃめちゃしてるよな・・・。

アイテムを挿入するときに

item.mask = LVIF_TEXT | LVIF_PARAM;

LVIF_PARAMつけて、item.lParamにユニークなIDを入れておく。これをしないとソートされないので注意のこと。後はアイテムにLVIF_PARAMフラグをつけるけど、サブアイテムをセットするときにはフラグはつけない。つけると(なぜか)サブアイテムが表示されない状態になってしまいます。内部の実装がどうなってるのか今ひとつ分からないのですが・・・。

内容は
http://www.kumei.ne.jp/c_lang/sdk2/sdk_110.htm
ほとんどそのままなので特にこっちで言うこともないかな・・・。それにしても比較関数の不細工さは何とかならないもんか。ListView_xxxはマクロ関数で、内部的にはウインドウにメッセージを投げてるのね。そんなオーバーヘッドでかいことする必要あるのか?とか思うんだけど、そういうもんなのかね。

ま、とりあえずやりたいことは全部できた。後は組み合わせるだけですね。ソート可能なリストビューにアクティブなプロセスの時間を載せれば完成かな?