ListView で行毎に背景色と文字色を変える方法
リストビューコントロールで、行毎に色を変更する方法について説明します。
上記のような表示にするためには、カスタムドロー (Custom Draw) という方法があります。リストビューは項目を描画するいくつかの段階で、カスタムドロー通知 (NM_CUSTOMDRAW) というのを送信します。これをうまく処理することで背景色を変更したり、いろいろ変更が加えられるわけです。
カスタムドローは多機能で、全部を説明するとごちゃごちゃしてわかりにくくなりますので、ここでは上図スクリーンショットのサンプルプログラムの動作に 絞って解説します。
まず、前提は描画にはいくつかの段階が定義されていると いうことです。一番の大枠は次のようになります。
つまり、
- 描画前
- [描画]
- 描画後
- 消去前
- [消去]
- 消去後
- (1 に戻る)
さらに、リストビューではそれぞれの項目(行)を描画するために、もうひとつ小さなサイクルがあります。それを組み込むと上記のサイク ルの 2 の描画段階がさらに細分化され、次のようになります。
- 描画前
- [描画]
2.1 項目の描画前
2.2 [描画]
2.3 項目の描画後 - 描画後
- 消去前
- [消去]
5.1項目の消去 前
5.2 [消去]
5.3 項目の消去後 - 消去後
- (1 に戻る)
さて、通常はどのようになるかというと、何も しないと "描画前" 通知しか受け取りません。項目(行) 毎にいろを変えてやりたいのですが、項目毎の通知を受け取りたいのですが、項目の描画前通知は通常はプログラムに通知されません。"項目の描画前通知" をプログラムに送るようにするためには、"描画前" 通知を受け取ったときに、ウィンドウプロシージャで CDRF_NOTIFYITEMDRAW という値を返してやればよいの です。そうすると、項目毎の描画前通知を受け取ることができるようになります。
項目毎の描画通知を受け取りさえすれば、そこで渡されるフォントや背景色の情報をプログラムから書き換えます。フォント情報を書き換えたら、ウィンドウプロシージャでは CDRF_NEWFONT 値を返します。
それぞれの通知コードは、"描画前通知" = CDDS_PREPAINT 通知、"項目毎の描画前通知" = CDDS_ITEMPREPAINT です。
以上を踏まえ、コードを見てください。
LRESULT OnNotiry
(HWND hwnd, INT idFrom, NMHDR* pnmhdr) {
if ( pnmhdr->code == NM_CUSTOMDRAW ) {
LPNMLVCUSTOMDRAW lpCustomDraw = (LPNMLVCUSTOMDRAW) pnmhdr;
switch ( lpCustomDraw->nmcd.dwDrawStage ) {
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
lpCustomDraw->clrText = RGB ( 209, 0, 0);
lpCustomDraw->clrTextBk = RGB ( 209, 240, 179);
return CDRF_NEWFONT;
}
}
return FORWARD_WM_NOTIFY
(hwnd, idFrom, pnmhdr, DefWindowProc );
}
これは、WM_NOTIFY メッセージのメッセージハンドラです。通知コード NM_CUSTOMDRAW だけを処理しています。NM_CUSTOMDRAW 通知の場合には、引数として渡される pnmhdr は NMLVCUSTOMDRAW データへのポインタになります。
ちなみに、このデータ構造は次のような形をしています。
実行時のデータを見ると次のような形をしています。
0:001> dt tagNMLVCUSTOMDRAW -r2
+0x000
nmcd
: tagNMCUSTOMDRAWINFO
+0x000
hdr
: tagNMHDR
+0x000
hwndFrom
: Ptr32 HWND__
+0x004
idFrom
: Uint4B
+0x008
code
: Uint4B
+0x00c
dwDrawStage : Uint4B
+0x010
hdc
: Ptr32 HDC__
+0x000
unused
: Int4B
+0x014
rc
: tagRECT
+0x000
left
: Int4B
+0x004
top
: Int4B
+0x008
right
: Int4B
+0x00c
bottom
: Int4B
+0x024
dwItemSpec
: Uint4B
+0x028
uItemState
: Uint4B
+0x02c
lItemlParam : Int4B
+0x030
clrText
: Uint4B
+0x034
clrTextBk
: Uint4B
+0x038
iSubItem
: Int4B
+0x03c
dwItemType
: Uint4B
+0x040
clrFace
: Uint4B
+0x044
iIconEffect : Int4B
+0x048
iIconPhase
: Int4B
+0x04c
iPartId
: Int4B
+0x050
iStateId
: Int4B
+0x054
rcText
: tagRECT
+0x000
left
: Int4B
+0x004
top
: Int4B
+0x008
right
: Int4B
+0x00c
bottom
: Int4B
+0x064
uAlign
: Uint4B
尚、行番号 (インデックス) はこの中の dwItemSpec に格納されています。
話を元に戻すと、このようなデータ構造になっていますので、NMHDR* を LPNMLVCUSTOMDRAW* にキャスト出来ます。
描画サイクルの値は NMCUSTOMDRAW の dwDrawStage に格納されています。ですから、上記 OnNotify 関数では dwDrawStage の値をチェックし、それが CDDS_PREPAINT であれば CDRF_NOTIFYITEMDRAW を返して終了。CDDS_ITEMPREPAINT のときに、NMLVCUSTOMDRAW から取得できる文字色 (clrText) と背景色 (clrTextBk) を変更して CDRF_NEWFONT を返して終了しています。
最後に、他のコモンコントロールの動作に影響を与えないように WM_NOTIFY で処理しなかった通知は DefWindowProc にフォワードしておきます。それが最後の FORWARD_WM_NOTIFY マクロの部分です。
以上、少しデータを細かく記載しましたが要は上記の OnNotify のようなコードを差し挟むだけで、文字色等を変更することが可能ということです。
サンプルコードのダウンロード [customdrawlv.zip]