SDK Index Previous page Next page

ListViewで一行全体を選択


はじめに

ListViewで一行全体を選択するようにする方法を説明します。ただし、Comctl32.dllのversion 4.70(IE3.0に付属)以降ならLVS_EX_FULLROWSELECTという拡張スタイルを使えばほぼ同じことが出来るので、IE3.0以降専用で良いのならこちらを使った方が簡単です。
今回作ったサンプルプログラムを実行すると次のようなリストビューが出来ます。

listview

リストビューアイテムはオーナードローを使って描いてありますが、かなり手を抜いているので拡張スタイルを使ったり詳細表示以外の表示をするとおかしくなります。もっとも拡張スタイルがあるのはversion 4.70以降なのでその時は、LVS_EX_FULLROWSELECTを使えばいいだけですが。あと、文字の表示も少しLVS_EX_FULLROWSELECTと違います。方法がよく分からなかったので。

プログラム

WinMainとInitWindowの部分はいつもと同じなので省略します。

1 ウインドウプロシージャ

ウインドウプロシージャです。
int CALLBACK WndProc(HWND hWnd, unsigned wMessage,
                  WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static  HWND    hList;

    switch (wMessage)
    {
        case WM_CREATE:
            CenterWindow(hWnd);
            hList=InitListView(hWnd);
            return  0;

        case WM_DESTROY:
            ImageList_Destroy(hImage);
            PostQuitMessage(0);
            return  0;

        case WM_SIZE:
            MoveWindow(hList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            return  0;

        case WM_PAINT:
            hdc = BeginPaint (hWnd, &ps);
            EndPaint (hWnd, &ps);
            return  0;
        case WM_DRAWITEM:
            DrawListItem((LPDRAWITEMSTRUCT)lParam);
            return TRUE;
    }
    return (DefWindowProc(hWnd, wMessage, wParam, lParam));
}
WM_CREATEの時に、リストビューを作って、WM_SIZEでウインドウの大きさに合わせてリストビューの大きさを変えています。また、オーナードローを使うのでWM_DRAWITEMの中で、リストビューのアイテムを描画します。

2 リストビューの初期化

InitListView関数で、リストビューを作って初期化しています。長い関数ですがアイテムの追加部分が大部分です。LVS_OWNERDRAWFIXEDスタイルがついている以外は何の変哲もないリストビューなので説明は省きます。リストビューについては、リンクページにある、猫でもわかるプログラミング というサイトに詳しく載っています。また、サブクラス化をしていますがその説明は後で述べます。
HWND InitListView(HWND hWnd)
{
    HWND        hList;
    LV_COLUMN   lvcol;
    LV_ITEM     item;

    InitCommonControls();
    hList = CreateWindowEx(0,
                    WC_LISTVIEW, "",
                    WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_OWNERDRAWFIXED,
                    0, 0, 0, 0,
                    hWnd,
                    (HMENU)ID_LISTVIEW,
                    Instance,
                    NULL);

    if(hList==NULL) return NULL;

    //リストビューのサブクラス化
    OrgListViewProc = (WNDPROC) SetWindowLong(hList,GWL_WNDPROC, (LONG) NewListViewProc);

    hImage=ImageList_Create(16,16,ILC_COLOR | ILC_MASK,4,0);
    ImageList_AddIcon(hImage,LoadIcon(Instance, MAKEINTRESOURCE(IDI_IMAGE1)));
    ImageList_AddIcon(hImage,LoadIcon(Instance, MAKEINTRESOURCE(IDI_IMAGE2)));
    ImageList_AddIcon(hImage,LoadIcon(Instance, MAKEINTRESOURCE(IDI_IMAGE3)));
    ImageList_AddIcon(hImage,LoadIcon(Instance, MAKEINTRESOURCE(IDI_IMAGE4)));

    ListView_SetImageList(hList,hImage,LVSIL_SMALL);

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

    lvcol.fmt = LVCFMT_RIGHT;
    lvcol.cx = 40;
    lvcol.pszText = "数量";
    lvcol.iSubItem = 1;
    ListView_InsertColumn(hList, 1, &lvcol);

    lvcol.fmt = LVCFMT_LEFT;
    lvcol.cx = 200;
    lvcol.pszText = "説明";
    lvcol.iSubItem = 2;
    ListView_InsertColumn(hList, 2, &lvcol);
//
    item.mask = LVIF_TEXT | LVIF_IMAGE;
    {
        int     i;
        char    text[50];

        for(i=0;i<20;i++){
            wsprintf(text,"アイテム %d",i+1);
            item.pszText=text;
            item.iItem = i;
            item.iSubItem = 0;
            item.iImage = i % 4;
            ListView_InsertItem(hList, &item);

            wsprintf(text,"%d",(i+1)*100);
            item.pszText=text;
            item.iItem = i;
            item.iSubItem = 1;
            ListView_SetItem(hList, &item);

            wsprintf(text,"%d個目のアイテム",i+1);
            item.pszText=text;
            item.iItem = i;
            item.iSubItem = 2;
            ListView_SetItem(hList, &item);
        }
    }
    return hList;
}

3 リストビューアイテムを描画する

オーナードローのメインです。ちょっと長いですが、コメントをいっぱい付けたので分かると思います。
//リストビューアイテムを描画する
void DrawListItem(LPDRAWITEMSTRUCT lpDraw)
{
    HWND        hList;              //リストビューのハンドル
    HDC         hdc;                //デバイスコンテキスト
    RECT        rc,rcAll,rcClient;              
    HBRUSH      hBrush;         //背景描画用のブラシハンドル
    COLORREF    Color;
    char        Text[256];
    int         SubItem;
    LVCOLUMN    LvColumn;   //列項目取得用の構造体
    LVITEM      LvItem;         //アイテム情報取得用の構造体
    HIMAGELIST  hImage;     //イメージリストのハンドル
    int         SubItemNum;

    hList=lpDraw->hwndItem;
    hdc = lpDraw->hDC;


    //デバイスコンテキストを保存
    SaveDC(hdc);
    //文字の背景の表示モードを非透過に設定する。
    SetBkMode(hdc,OPAQUE);
    //ODS_SELECTEDフラグがあるときには、強調表示する。
    if (lpDraw->itemState & ODS_SELECTED) {
        //背景色の取得
        hBrush=CreateSolidBrush (GetSysColor(COLOR_HIGHLIGHT));
        SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
        SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
    }else{
        Color=ListView_GetTextBkColor(hList);
        //ListView_SetTextBkColorが一度もされてないときには、ff000000という色になるので
        //その時には、システムの色を使う。これをしないと黒になってしまう。
        if(Color & 0xff000000) Color=GetSysColor(COLOR_WINDOW);
        hBrush=CreateSolidBrush (Color);
    }

    //列見出しのフォーマット情報を取得する。
    ZeroMemory(&LvColumn,sizeof(LvColumn));
    LvColumn.mask=LVCF_FMT;
    ListView_GetColumn(hList,0,&LvColumn);

    //背景を埋める
    ListView_GetItemRect(hList,lpDraw->itemID,&rcAll,LVIR_BOUNDS);
    ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_LABEL);
    rcAll.left=rc.left;

    GetClientRect(hList,&rcClient);
    if(rcAll.right//アイテムの情報を取得します。表示するイメージのインデックスもいるのでListView_GetItemを使う。
    ZeroMemory(&LvItem,sizeof(LvItem));
    LvItem.iItem=lpDraw->itemID;
    LvItem.iSubItem=0;
    LvItem.mask=LVIF_TEXT | LVIF_IMAGE;
    LvItem.pszText=Text;
    LvItem.cchTextMax=sizeof(Text);
    ListView_GetItem(hList,&LvItem);

    //イメージリストのハンドルを取得する
    hImage=ListView_GetImageList(hList,LVSIL_SMALL);
    //イメージリストが登録されているときだけ、イメージを表示する。
    //イメージ番号は、iImageメンバにある。
    if(hImage){
        ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_ICON);
        ImageList_Draw(hImage,LvItem.iImage,hdc,
            rc.left,rc.top,
            //イメージを強調表示するかどうかを設定する。
            ((lpDraw->itemState & ODS_SELECTED)? ILD_FOCUS:ILD_NORMAL) |ILD_TRANSPARENT);
    }

    //文字列を表示する。ListView_GetItemRectを使うと表示する領域が分かる。
    ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_LABEL);
    DrawListItemText(hdc,Text,&rc,LvColumn.fmt);

    //サブアイテムの数を取得する
    SubItemNum=Header_GetItemCount(ListView_GetHeader(hList))-1;
    //サブアイテムも同様に表示する。
    for(SubItem=1;SubItem<=SubItemNum;SubItem++){
        //背景のクリア
        ListView_GetSubItemRect(hList,lpDraw->itemID,SubItem,LVIR_BOUNDS,&rc);
        FillRect(hdc,&rc,hBrush);
        //列項目のフォーマットを得ます。構造体はさっき設定したのでそのまま使う。
        ListView_GetColumn(hList,SubItem,&LvColumn);
        ListView_GetSubItemRect(hList,lpDraw->itemID,SubItem,LVIR_LABEL,&rc);
        //アイテムの情報を取得します。文字列だけなので今度はListView_GetItemTextを使います。
        ListView_GetItemText(hList,lpDraw->itemID,SubItem,Text,sizeof(Text));
        DrawListItemText(hdc,Text,&rc,LvColumn.fmt);
    }

    if (lpDraw->itemState & ODS_FOCUS){
        DrawFocusRect(hdc,&rcAll);
    }
    
    //作ったブラシの削除
    DeleteObject(hBrush);
    //デバイスコンテキストの復元
    RestoreDC(hdc,-1);
}

4 文字列描画関数

文字列の表示には、DrawTextEx関数を使っています。構造体の設定とかで長くなるので別の関数にまとめました。右寄せにしたときの動作が、ちょっとオリジナルと違うのですがそのままにしてあります。
//文字列の表示
void DrawListItemText(HDC hdc,char *Text,RECT *rc,int fmt)
{
    DRAWTEXTPARAMS  DrawTextExParam;
    int     Justify;

    //隣り合う文字列がくっつかないように左右にマージンをつける。
    //ただし、右マージンは文字の配置を右寄せにしたときだけ付ける。
    DrawTextExParam.cbSize=sizeof(DrawTextExParam);
    DrawTextExParam.iLeftMargin=2;
    DrawTextExParam.iRightMargin=0;
    DrawTextExParam.iTabLength=0;
    DrawTextExParam.uiLengthDrawn=0;

    //サブアイテムの文字の配置を調べて、DrawTextEx用のスタイルに直す。
    if((fmt & LVCFMT_JUSTIFYMASK)==LVCFMT_LEFT)         Justify=DT_LEFT;
    else if((fmt & LVCFMT_JUSTIFYMASK)==LVCFMT_RIGHT){  Justify=DT_RIGHT;
                                                        DrawTextExParam.iRightMargin=6;}
    else                                                Justify=DT_CENTER;
    //文字列の表示 左右マージンをつけるのでDrawTextExを使っている。
    DrawTextEx(hdc,Text, lstrlen(Text),rc,
        Justify | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS,
        &DrawTextExParam);
}

5 サブクラス化

アイテムを選択したときの青い四角が常にウインドウの右端までのびるようにするために、 リストビューをサブクラス化します。
これは通常リストアイテムの右側の空き部分に描画してもクリップされてしまい描画できないので、そのクリップ範囲を変更するためです。クリップされるのは、WM_PAINTメッセージの中で、BeginPaintを呼び出したときです。 なので、WM_PAINTメッセージを先に捕まえてクリップ範囲を変更してしまいます。
WNDPROC OrgListViewProc;

int CALLBACK NewListViewProc(HWND hWnd, unsigned wMessage,
                  WPARAM wParam, LPARAM lParam)
{
    RECT        rcClient,rcClip;
    PAINTSTRUCT ps;
    HDC         hdc;

    switch (wMessage)
    {
        case WM_PAINT:
            hdc = BeginPaint (hWnd, &ps);       //ウインドウにクリップ領域が設定される
            GetClientRect(hWnd,&rcClient);
            GetClipBox(hdc,&rcClip);            //クリップ領域を得る
            rcClip.right=rcClient.right;        //クリップ領域をウインドウの右端まで広げる
            InvalidateRect(hWnd,&rcClip,FALSE); //新たにクリップ領域を設定する
            EndPaint (hWnd, &ps);               //ウインドウのクリップ領域は破棄される
            break;
            //この後、デフォルトのプロシージャがBeginPaintを実行したときには、
            //新しく作った方のクリップ領域が設定されるので、右端まで描画できるようになる。
    }

    return CallWindowProc(OrgListViewProc, hWnd, wMessage,wParam, lParam);
}

おわりに

今回は、リストビューのオーナードローについて説明しました。オーナードローはややこしい代わりにいろいろなことが出来るので覚えておいて損はないと思います。


to sdk prev next