リストコントロールのTIPSあれこれ

昨日は久しぶりにMFCでコードを書きました。データを一覧表示するのにリストコントロールのレポート機能を使ったのですが、使い方をもうすっかり忘れてしまっていてずいぶん時間をかけてしまいました。僕がやることなんて大抵いつも同じなのでテンプレート用サンプルを作成しておけば、後々あちこちのプロジェクトを引っかき回すこともないかと考え、テンプレート的サンプルプログラムを作成しました。今回はどうしても編集機能が欲しかったので、エディットボックスをカラムに貼り付けて編集機能を実装するというサンプルも取り込みました。前から思っていたのですが、リストコントロールは編集機能がない(先頭カラムは除く)のが難点だと思っていたのでこの解決はウレシイですね。それにしても、すごいこと考える人がいるものです。僕には絶対思いつかない.....

以下のコードですが、コントロール変数としてViewクラスにm_listctlを作成しているという前提です。詳細は添付のサンプルプロジェクトを確認してみてください。

構築

最初に見出し作成します。InsertColumnを使います。この部分は大抵コピペですみますね。h[],w[],f[]はそれぞれ見出し文字列・列幅・見出文字位置が入っています。(サンプルコード参照)

LV_COLUMN	lvc;
lvc.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
for(int i = 0; i<5; i++)
{
  lvc.iSubItem = i;
  lvc.pszText = h[i];
  lvc.cx = w[i];
  lvc.fmt = f[i];
  m_listctl.InsertColumn(i,&lvc);
}

データを表示する

OnUpdate()ハンドラでやっています。個々の項目を更新するのが面倒なのでいつも、一旦すべてのデータ削除してから再度データ設定しています。データ量が多かったり、パフォーマンスを気にする場合は不利かもしれません。InsertItemでは、一行分のデータをリストコントロールに設定します。

LV_ITEM	lvi;
m_listctl.SetRedraw(FALSE);
m_listctl.DeleteAllItems();
Csampledata* sample = NULL;
while((sample = pDoc->get_data(pos)) != NULL)
{
  lvi.iItem = pos;
  lvi.iSubItem = 0;
  lvi.mask = LVIF_TEXT|LVIF_PARAM;
  lvi.pszText = LPSTR_TEXTCALLBACK;
  lvi.lParam = (LPARAM)sample;
  m_listctl.InsertItem(&lvi);
  pos++;
}
m_listctl.SetRedraw(TRUE);

InsertItemで設定した1行分のデータを、LVN_GETDISPINFOメッセージをハンドリングしてカラム単位に設定します。

sample=(Csampledata*)((LV_DISPINFO*)pDispInfo)->item.lParam;
switch(((LV_DISPINFO*)pDispInfo)->item.iSubItem)
{
case 0:
  strcpy(pDispInfo->item.pszText,sample->col1.c_str());
  break;
case 1:
  strcpy(pDispInfo->item.pszText,sample->col2.c_str());
  break;
case 2:
  str.Format("%d",sample->col3);
  strcpy(pDispInfo->item.pszText,str);
  break;
case 3:
  str.Format("%d",sample->col4);
  strcpy(pDispInfo->item.pszText,str);
  break;
case 4:
  strcpy(pDispInfo->item.pszText,sample->col5.c_str());
  break;
}
*pResult = 0;

リストの大きさをウィンドウに合わせて伸縮させる

これはリストコントロールに限りませんが、このあたりはCocoa風にできるとウレシイですね。ViewクラスのOnSizeハンドラに実装します。数値は各自調整のこと。

RECT rect;
if (m_listctl.m_hWnd != NULL){
   (m_listctl.GetParent())->GetClientRect(&rect);
  rect.top = 30;	rect.left  += 13;
  rect.bottom -= 13;	rect.right -= 13;
  m_listctl.MoveWindow(&rect,TRUE);
}

1行ごとに背景色をつける

リストにありがちなUIです。リストコントロールのNM_CUSTOMDRAWハンドラに実装します。最初に、CDRF_NOTIFYITEMDRAWとCDRF_NOTIFYSUBITEMDRAWメッセージを受信するよう設定してその後、1行ずつ描画の際に目セージが飛んでくるので偶数行(あるいは奇数行)背景色を設定します。このコードなどずっとコピペで使い回しているのでもうこれ以上詳しく書けない...

LPNMCUSTOMDRAW pNMCD = reinterpret_cast(pNMHDR);
LPNMLVCUSTOMDRAW lpnmlv = (LPNMLVCUSTOMDRAW)pNMCD;
if (lpnmlv->nmcd.dwDrawStage==CDDS_PREPAINT)
{
  *pResult=(long)CDRF_NOTIFYITEMDRAW;
  return;
}
if((lpnmlv->nmcd.dwDrawStage&CDDS_ITEMPREPAINT)==CDDS_ITEMPREPAINT)
{
  if (lpnmlv->nmcd.dwItemSpec%2)
  {
    lpnmlv->clrTextBk = RGB(240,240,240);
  }
  *pResult = (long)CDRF_NOTIFYITEMDRAW;
}else{
  *pResult = 0;
}
return;

選択を列単位に変更

デフォルトではカラム単位での選択ですが、行選択の方がしっくりするのは僕だけでしょうか?

m_listctl.SendMessage(
 LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT
);

グリッド線を表示

これは、場合によりけりですね。

m_listctl.SetExtendedStyle(
  (m_list.GetExtendedStyle()|LVS_EX_GRIDLINES)
);

先頭列にチェックボックスをつける

別にSingleSelectionプロパティで複数選択を制御すればいいだけのことですが、見た目上これが要求されることが(僕の場合)よくあります。

m_listctl.SetExtendedStyle(
  (m_list.GetExtendedStyle()|LVS_EX_CHECKBOXES)
);

列ごとに文字色を変える

「1行ごとに背景色をつける」と同じ手法でできます。大抵はカラムに表示するデータの内容によって色を変えることになるかと思います。1度色を変えるとその後ずっとその値を引きずりますから元に戻す必要があります。

LPNMCUSTOMDRAW pNMCD = reinterpret_cast(pNMHDR);
LPNMLVCUSTOMDRAW lpnmlv = (LPNMLVCUSTOMDRAW)pNMCD;
if (lpnmlv->nmcd.dwDrawStage==CDDS_PREPAINT)
{
  *pResult=(long)CDRF_NOTIFYITEMDRAW;
  return;
}
if((lpnmlv->nmcd.dwDrawStage&CDDS_ITEMPREPAINT)==CDDS_ITEMPREPAINT)
{
  Csampledata* sample;
  sample = (Csampledata*)lpnmlv->nmcd.lItemlParam;
  switch(lpnmlv->iSubItem)
  {
  case 2: //列3
    if (sample->col3 < 3)
      {lpnmlv->clrText = RGB(255,0,0);}
      else{lpnmlv->clrText = RGB(0,0,0);}
    break;
  case 3: //列4
     if (sample->col4 > 20)
       {lpnmlv->clrText = RGB(0,0,255);}
       else{lpnmlv->clrText = RGB(0,0,0);}
     break;
  default:
    lpnmlv->clrText = RGB(0,0,0);
  }
  *pResult = (long)CDRF_NOTIFYITEMDRAW;
}else{
  *pResult = 0;
}
return;

カラムを編集する

先にも書いたようにCodeProject: Editing Sub-Items in List Control. Free source code and programming helpを参考に実装しました。ほぼ、ここのコードのとおりにやっていますのでコードは割愛。 実際のアプリではむしろ作成したエディットボックスを非表示にすることの方が問題になるかもしれません 。

こうしてみると、やっぱCocoaの方が洗練されている気がするのは贔屓目かなぁ?どうも、MFCは冗長な気がします。C#になれば変わるのだろうか?化石な僕は未だC#を触ったことがありません。

スクリーンショット1
スクリーンショット2

サンプルプロジェクトはVisualStudio2005です。

添付サイズ
listctl_sample.tgz25.17 KB

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/271

Comments