概要

リストビューのサブアイテムにイメージを表示するサンプルである。 サブアイテム(2列目以降)にイメージを表示する場合は、拡張スタイルでLVS_EX_SUBITEMIMAGESを設定する必要があります。 ただし、サブアイテムにイメージを表示すると、1列目にもイメージが表示されてしまいます。 ここでは、1列目の列幅を0にすることによりイメージを見えないようにしています。 どうせ見えないイメージなのでイメージ番号を-1に設定しています。 列幅を0にしてもディバイダーをドラッグしたりダブルクリックすると変更できてしまうので、リストビューをサブクラス化し、列幅を変更する際に発生するメッセージを無視するようにしています。 リストビューの子ウィンドウがヘッダーコントロールなので、ヘッダーコントロールのメッセージはリストビューのWM_NOTIFYメッセージとして発生します。 したがってダイアログボックスには届きません。 リストビューのサブクラス化によってメッセージを捕らえてメッセージを無視します。 イメージはメモリ上にプログラムで描画しています。リストビューの場合、リストビューを閉じた場合のイメージリストの破棄は、リストビュー作成時に何も指定しないと自動的に破棄されます。 リストビューは作成したウィンドウの子ウィンドウです。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

_tWinMain関数

Windowsから最初に_tWinMain関数が呼び出されます。 ウィンドウを作成する場合は、RegisterClass APIによりウィンドウクラスを定義してからCreateWindow APIを呼び出しウィンドウを作成します。 Windowsは入力等のイベントが発生するとアプリケーションにメッセージを送付します。 メッセージはキューに保管されます。アプリケーションはメッセージを取り出し、該当ウィンドウにメッセージを配信します。 メッセージの取り出しから配信までループで処理を行いウィンドウから終了メッセージが届くと、ループを抜けるように記述します。 これらの一連の処理は、通常にCreateWindow APIの後に記述しこれをメッセージループと呼んでいます。 詳細は以下を参照してください。
メッセージループ

WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)ウィンドウプロシージャー

RegisterClass APIにより登録することによりWindowsから呼び出されます。
第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。
自分で処理しないメッセージはDefWindowProc APIに渡せばWindowsが標準的な処理を行ってくれます。

case WM_CREATE:

ウィンドウの初期化時に呼び出されます。
CreateWindowEx APIでこのウィンドウの子ウィンドウであるリストビューを作成します。
リストビューの位置、大きさは0で作成し、後でウィンドウサイズを修正します。
リストビューのハンドルは後で使用するのでスタティック変数に保存しておきます。
ListView_GetExtendedListViewStyleマクロによりリストビューのスタイルを取得します。
取得したスタイルに以下のスタイルを追加し、ListView_SetExtendedListViewStyleマクロによりスタイルを設定します。

LVS_EX_GRIDLINES        グリッド線を入れる
LVS_EX_SUBITEMIMAGES    イメージを追加
LVS_EX_FULLROWSELECT    1行選択を可能にする
ただし、サブアイテムにイメージを表示すると、1列目にもイメージが表示されてしまいます。 ここでは、1列目の列幅を0にすることによりイメージを見えないようにしています。 どうせ見えないイメージなのでイメージ番号を-1に設定しています。 列幅を0にしてもディバイダーをドラッグしたりダブルクリックすると変更できてしまうので、リストビューをサブクラス化し、列幅を変更できないようにします。
サブクラス化とは、コモンコントロールであるリストビューのウィンドウプロシージャーをユーザー定義のListViewProc関数に置き換えることを意味します。
これによりリストビューの動作をユーザーが自由にカスタマイズできます。カスタマイズの必要のない機能は、もとのリストビューのプロシージャーを呼び出せば済みます。
GetWindowLongPtr APIにより現在のリストビューのプロシージャーを取得しグローバル変数に保存しておきます。
SetWindowLongPtr APIにより新しいリストビューのプロシージャーListViewProcを登録します。
ListView_InsertColumnによりヘッダー(列の見出し)を作成します。
ImageList_Create APIによりイメージリストを作成します。
GetDCによりウィンドウのデバイスコンテキストを取得します。
イメージリストに登録するために画像を描画するビットマップをCreateDIBSection APIにより作成します。 make_line_img関数によりDIBに指定された線種を描画しイメージリストに登録します。
DeleteObjectによりビットマップを削除します。
ListView_SetImageListマクロによりイメージリストをリストビューに関連づけます。
ListView_InsertItemマクロにより1列目のアイテムを設定します。
ListView_SetItemマクロにより2列目のアイテムを設定します。
line_style関数により3列目にイメージとイメージ名を設定します。

case WM_SIZE:

ウィンドウのサイズが変更されたときに呼び出されます。
MoveWindows APIを呼び出しリストビューのサイズを変更します。

case WM_DESTROY:

ウィンドウが閉じるときに呼び出されます。
PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

ListViewProc

サブクラス化を行うためのリストビューウインドウプロシージャーです。
リストビューでカスタマイズしたいメッセージを処理します。
処理後、元のリストビューウインドウプロシージャーを呼び出す場合は、CallWindowProc APIを使用します。
元のリストビューウインドウプロシージャーを呼び出さない場合は、TRUEを返します。
ここでは、リストビューのディバイダーが動かされた時のメッセージをトラップして列が0の場合は、TRUEを返して、元のリストビューの処理を無視します。

make_line_img

指定されたデバイスコンテキストと互換性のあるメモリデバイスコンテキストを作成し、そこに指定されたパターンの線を描画し、イメージリストに追加します。
CreateCompatibleDC APIにより指定されたデバイスコンテキストと互換性のあるメモリデバイスコンテキストを作成します。
SelectObject APIによりビットマップにデバイスコンテキストを関連付けます。
CreatePen APIにより線幅1で指定したパターンのペンを作成します。
SelectObject APIにより作成したペンを選択します。
PatBlt APIによりビットマップを白で塗りつぶします。
SetBkMode APIにより背景モードを描画時に背景をそのまま残す設定にします。
MoveToEx APIによりビットマップの左中央に座標を移動します。
LineTo APIにより左中央から右中央に線分を描画します。
ImageList_AddMasked APIにより白を透明色に設定してイメージリストにビットマップを設定します。

line_style

イメージとイメージ名を設定します
LV_ITEM構造体のmaskメンバーにLVIF_IMAGE定数を設定し、Imageメンバーにイメージリストの番号を設定します。
ListView_SetItemマクロによりリストビューのアイテムとして設定します。

プログラムソース

listview3.cpp


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

#pragma comment(lib,"comctl32.lib")

#define ID_LISTVIEW 100


#define IMAGE_WIDTH 96  //      1個の画像の横幅
#define IMAGE_HIGHT 16  //      1個の画像の高さ

WNDPROC default_listview_proc;  //      ディフォルトリストビュープロシージャー

//  サブクラス化を行うためのリストビューウインドウプロシージャー
LRESULT CALLBACK    ListViewProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam);

//      メモリ上に線を描画しイメージリストに登録する
//      nが-1以外の時はnで指定されたイメージを置き換える
void make_line_img(HIMAGELIST hImg,HDC hdc,int s,COLORREF color,int n=-1);
void make_line_img(HBITMAP* ,HIMAGELIST hImg,HDC hdc,int s,COLORREF color,int n=-1);

//      リストビューに線種を示すアイコンと文字列を登録する
void line_style(HWND hList,int Item,int subitem,int s);

//      線種を管理する構造体

struct LINE_STYLE{
        TCHAR* text;
        int style;
        COLORREF color;
};

//      初期時の線種

LINE_STYLE ls[]={
        {       TEXT("線1"),PS_SOLID , RGB(0xff,0,0) },
        {       TEXT("線2"),PS_DASH  , RGB(0,0xff,0) },
        {       TEXT("線3"),PS_DOT   , RGB(0,0,0xff) },
        { 0,0,0 }};

TCHAR* szClassName=TEXT("リストビュー3");
HINSTANCE hInst;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,
        TCHAR* lpszCmdLine, int nCmdShow){
        HWND hWnd;
        MSG lpMsg;
        WNDCLASS myProg;
        hInst=hInstance;

        if (!hPreInst) {
                myProg.style = CS_HREDRAW | CS_VREDRAW;
                myProg.lpfnWndProc = WndProc;
                myProg.cbClsExtra = 0;
                myProg.cbWndExtra = 0;
                myProg.hInstance = hInstance;
                myProg.hIcon = NULL;
                myProg.hCursor = LoadCursor(NULL, IDC_ARROW);
                myProg.hbrBackground = (HBRUSH__ *)GetStockObject(WHITE_BRUSH);
                myProg.lpszMenuName = NULL;
                myProg.lpszClassName = szClassName;
                if (!RegisterClass(&myProg))
                        return FALSE;
    }
        hWnd = CreateWindow(szClassName,
                TEXT("リストビュー3"),
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                350,
                150,
                NULL,
                NULL,
                hInstance,
                NULL);
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        while (GetMessage(&lpMsg, NULL, 0, 0)){
                TranslateMessage(&lpMsg);
                DispatchMessage(&lpMsg);
        }
        return int(lpMsg.wParam);
}

// ウィンドウプロシージャー

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
        static HIMAGELIST hImg;
        static HWND hList;
    LV_COLUMN lvcol;
        LV_ITEM item;

        HDC hdc;
        DWORD ds;
        int n;

        switch(msg){
case WM_CREATE:{
        hList = CreateWindowEx(0,
                        WC_LISTVIEW, TEXT(""),
                        WS_CHILD | WS_VISIBLE | LVS_REPORT,
                        0, 0, 0, 0,
                        hWnd,
                        (HMENU)ID_LISTVIEW,
                        hInst,
                        NULL);
//      リストビュー拡張スタイルの設定
                ds=ListView_GetExtendedListViewStyle(hList);
                ds |= LVS_EX_GRIDLINES  | LVS_EX_SUBITEMIMAGES | LVS_EX_FULLROWSELECT;
                ListView_SetExtendedListViewStyle(hList,ds);
//      リストビューのサブクラス化
                default_listview_proc=(WNDPROC)GetWindowLongPtr(hList,GWLP_WNDPROC);
                SetWindowLongPtr(hList,GWLP_WNDPROC,(LONG_PTR)ListViewProc);
//      1列目ヘッダー登録 ダミー
        lvcol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
        lvcol.fmt = LVCFMT_LEFT;
                lvcol.cx = 0;
        lvcol.pszText = TEXT("");
        lvcol.iSubItem = 0;
        ListView_InsertColumn(hList, 0, &lvcol);
//      2列目ヘッダー登録                               
                lvcol.cx = 128;
        lvcol.pszText = TEXT("名称");
        lvcol.iSubItem = 1;
        ListView_InsertColumn(hList, 1, &lvcol);
//      3列目ヘッダー登録                               
        lvcol.cx = 256;
        lvcol.pszText = TEXT("線種");
        lvcol.iSubItem = 2;
        ListView_InsertColumn(hList, 2, &lvcol);

//      イメージリスト作成
                hImg = ImageList_Create(IMAGE_WIDTH , IMAGE_HIGHT , ILC_COLOR32, 5 , 0);
//      イメージリストの登録
                hdc=GetDC(hWnd);

                HBITMAP hbitmap;
                BITMAPINFO bmp;

        //      メモリ上にデバイス独立ビットマップを(DIB)作成する。
                ZeroMemory(&bmp,sizeof(bmp));
                bmp.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
                bmp.bmiHeader.biBitCount=32;
                bmp.bmiHeader.biPlanes=1;
                bmp.bmiHeader.biWidth=IMAGE_WIDTH;
                bmp.bmiHeader.biHeight=-IMAGE_HIGHT;
                hbitmap=CreateDIBSection(NULL,&bmp,DIB_RGB_COLORS,NULL , NULL,0);

                for(n=0 ; ls[n].text ; n++){
                        make_line_img( &hbitmap,hImg , hdc , ls[n].style , ls[n].color ,-1);
                }
                DeleteObject(hbitmap);

//  イメージリストをリストビューに関連付ける
                ListView_SetImageList(hList , hImg , LVSIL_SMALL);

                for(n=0;ls[n].text;n++){
//      1列目アイテムの登録
                        item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
                        item.pszText = TEXT("");
                        item.iItem = n;
                        item.iSubItem = 0;
                        item.lParam = n;
                        item.iImage=-1;
                        ListView_InsertItem(hList, &item);
//      2列目アイテムの登録
                        item.mask = LVIF_TEXT;
                        item.pszText = ls[n].text;
                        item.iSubItem = 1;
                        ListView_SetItem(hList, &item);
//      3列目アイテムの登録
                        line_style(hList , n , 2 , ls[n].style);
                }
                ReleaseDC(hWnd,hdc);
                break;
                           }
        case WM_SIZE:
                MoveWindow(hList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return(DefWindowProc(hWnd, msg, wParam, lParam));
    }
    return (0L);
}


//  サブクラス化を行うためのリストビューウインドウプロシージャー

LRESULT CALLBACK ListViewProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){
        NMHEADER* pnmhdr;
        switch(msg){
        case WM_NOTIFY:
                pnmhdr=(NMHEADER*)lParam;
                switch(pnmhdr->hdr.code){
                case HDN_ENDTRACK:      //      リストビューヘッダーのディバイダーのドラッグを終了したとき
                case HDN_BEGINTRACK:    //      リストビューヘッダーのディバイダーのドラッグを開始したとき
                case HDN_TRACK: //      リストビューヘッダーのディバイダーのドラッグ中
                case HDN_DIVIDERDBLCLICKW:      //      リストビューヘッダーのディバイダーでダブルクリックをしたとき
                        if(pnmhdr->iItem==0){
                                return TRUE;    //      ディフォルトのプロシージャーへ処理を渡さない(列幅変更メッセージを無視する)
                        }
                }
        }
    return  CallWindowProc((WNDPROC)default_listview_proc,hWnd,msg,wParam,lParam);
}

//      メモリ上に線を描画しイメージリストに登録する
//      nが-1以外の時はnで指定されたイメージを置き換える

void make_line_img(HBITMAP* hbitmap,HIMAGELIST hImg,HDC hdc,int s,COLORREF color,int n){
        HDC img_hdc;

        //      画面と互換性のあるデバイスコンテキストを作成
        img_hdc=CreateCompatibleDC(hdc);
        SelectObject(img_hdc , *hbitmap);

        HPEN hPen,hOldPen;
        hPen=CreatePen(s, 1, color );

        hOldPen=(HPEN__ *)SelectObject(img_hdc, hPen);

        PatBlt(img_hdc , 0 , 0 , IMAGE_WIDTH , IMAGE_HIGHT , WHITENESS);

        SetBkMode(img_hdc,TRANSPARENT); // 破線の隙間に色を付けない
        MoveToEx(img_hdc,0,IMAGE_HIGHT/2,NULL);
        LineTo(img_hdc,IMAGE_WIDTH,IMAGE_HIGHT/2);

        SelectObject(img_hdc, hOldPen);
        DeleteObject(hPen);

// イメージリストに登録する前にデバイスコンテキストを削除しなければならない
//      削除しないと真っ黒な画像が表示されるだけである。
        DeleteDC(img_hdc);
        if(n == -1)
                ImageList_AddMasked(hImg ,*hbitmap,RGB(255,255,255));
        else
                ImageList_Replace(hImg ,n,*hbitmap,0);
}


//      リストビューに線種を示すアイコンと文字列を登録する

void line_style(HWND hList,int Item,int subitem,int s){
        TCHAR* lst[]={  TEXT("PS_SOLID"),
                                        TEXT("PS_DASH"),
                                        TEXT("PS_DOT"),
                                        TEXT("PS_DASHDOT"),
                                        TEXT("PS_DASHDOTDOT"),0 };
        
        int ls[]={              PS_SOLID,
                                        PS_DASH,
                                        PS_DOT,
                                        PS_DASHDOT,
                                        PS_DASHDOTDOT,
                                        0 };
        LV_ITEM item;

        item.mask=LVIF_TEXT | LVIF_IMAGE;
        item.pszText = lst[s];
        item.iItem=Item;
        item.iSubItem = subitem;
        item.iImage=Item;
        ListView_SetItem(hList, &item);
}

ソースファイルと実行ファイルのダウンロード

ダウンロード listview3.zip(39.8kByte)

ZIPファイルに含まれるファイル

listview3.cpp
listview3.exe