ListView で行毎に背景色と文字色を変える方法

リストビューコントロールで、行毎に色を変更する方法について説明します。

上記のような表示にするためには、カスタムドロー (Custom Draw) という方法があります。リストビューは項目を描画するいくつかの段階で、カスタムドロー通知 (NM_CUSTOMDRAW) というのを送信します。これをうまく処理することで背景色を変更したり、いろいろ変更が加えられるわけです。

カスタムドローは多機能で、全部を説明するとごちゃごちゃしてわかりにくくなりますので、ここでは上図スクリーンショットのサンプルプログラムの動作に 絞って解説します。

まず、前提は描画にはいくつかの段階が定義されていると いうことです。一番の大枠は次のようになります。

つまり、

  1. 描画前
  2. [描画]
  3. 描画後
  4. 消去前
  5. [消去]
  6. 消去後
  7. (1 に戻る)
というサイクルです。

さらに、リストビューではそれぞれの項目(行)を描画するために、もうひとつ小さなサイクルがあります。それを組み込むと上記のサイク ルの 2 の描画段階がさらに細分化され、次のようになります。

  1. 描画前
  2. [描画]
    2.1 項目の描画前
    2.2 [描画]
    2.3 項目の描画後
  3. 描画後
  4. 消去前
  5. [消去]
    5.1項目の消去 前
    5.2 [消去]
    5.3 項目の消去後
     
  6. 消去後
  7. (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]

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説