Tutorial 31: ListView Control

今回はリストビューコントロールについての説明だ。
 メインソース   ヘッダファイル   リソース   実行結果 

Theory:

リストビューコントロールはツリービューやリッチエディットと同様にコモンコントロールの一種だ。初めて使うにしても、名前により大体想像はつくだろう。 例えば、エクスプローラの右ペインはリストビューコントロールである。リストビューコントロールはアイテムを表示するのに適したコントロールで、この点に関して言えば、リストボックスと同様であるがさらに驚くべき機能がある。

リストビューコントロールの作成方法は2通りある。1つ目の方法は非常に簡単なもので、リソースエディタにより作成するものだ。この場合、InitCommonControls関数をCALLすることを忘れてはならない。
もう1つの方法はCreateWindowEx関数をCALLするのだが、その際ウィンドウクラス名をSysListView32としなければならない。"WC_LISTVIEW"というウィンドウクラス名は間違いである。

リストビューコントロール内でデータを表示する方法は「アイコン」「スモールアイコン」「リスト」「レポートビュー」の4つである。これらの表示方法はサンプルプログラムを実行して、「icon view(Larage Icons)」「small icon view(Small Icons)」「(list view)List」「(report view)Details」を選択すればわかるだろう。
これらは単なる表示方法による違いで、元もとのデータは同じものだ。例えば、リストビューコントロールに大量のデータがあった場合、その中から必要なデータだけ表示することができたりする。ちなみに、レポートビューが一番情報量の多い表示方法である。
リストビューコントロールを作成するときにどの表示方法を使用するかを指定するようになっているが、もちろん後でも、GWL_STYLEフラグを指定してSetWindowLong関数をCALLすることにより変更することができる。

リストビューコントロールの作成方法がわかったところで、次に使用方法についての説明に移ろう。ここではリストビューコントロールの多くの特徴を理解してもらうためにレポートビューにフォーカスを当てることにする。使用方法は以下の通りである。

  1. クラス名をSysListView32として、CreateWindowEx関数をCALLしてリストビューコントロールを作成する。この時に、一番最初の表示方法も指定できる。
  2. (もしあれば)リストビューアイテムとして使用するイメージを作成、初期化する。
  3. リストビューコントロールがレポートビューを使用するのなら、リストビューコントロールに行を追加する。
  4. リストビューコントロールにアイテムとサブアイテムを挿入する。

●Columns

レポートビューには一つ以上の行がある。レポートビューのデータはテーブルと同じように考えることができるので、これらのデータはカラムと行を指定して編集することになる。 リストビューコントロールには少なくとも1行以上あることになっている。
レポートビュー以外の表示形式では、1行しか必要でないので行を挿入する必要はない。

リストビューにはLVM_INSERTCOLUMNを送信することによって行を挿入する。

LVM_INSERTCOLUMN
  • wParam = iCol
  • lParam = LV_COLUMN構造体へのポインタ

iColは0から始まる行番号だ。
LV_COLUMN構造体にはカラム情報が格納されており、以下のような定義となっている。

LV_COLUMN STRUCT imask dd ? fmt dd ? lx dd ? pszText dd ? cchTextMax dd ? iSubItem dd ? iImage dd ? iOrder dd ? LV_COLUMN ENDS

Field nameMeanings
imask

この構造体のどのメンバが有効かを表すフラグである。このフラグの意味するところは、この構造体の全てのメンバが同時に使用されるものではない、ということである。ちなみにこの構造体は入力にも出力にも使用される。

  • LVCF_FMTfmtが有効
  • LVCF_SUBITEMiSubItemが有効
  • LVCF_TEXTpszTextが有効
  • LVCF_WIDTHlxが有効

上記のフラグは組み合わせて指定することが可能で、例えばカラム文字列を記述したければ、pszTextメンバに文字列へのポインタをセットしなければならない。そして、pszTextメンバに値がセットされていることを明示するために、LVCF_TEXTフラグを指定しなければならない。さもないとWindowsはpszTextの値を無視してしまう。

fmt カラム内のアイテム、サブアイテムをどのように配置するかを指定する。利用できる値は以下の通りだ。
  • LVCFMT_CENTER:テキストをセンタリングする
  • LVCFMT_LEFT:テキストを左に寄せる
  • LVCFMT_RIGHT:テキストを右に寄せる
lx カラムの幅をピクセル単位で指定する。後でLVM_SETCOLUMNWIDTHメッセージによりカラム幅を変更することもできる。
pszText この構造体がカラムプロパティをセットするために使用されている場合、カラム名へのポインタとなり、この構造がカラムプロパティを受け取るために使用される場合は、現在セットされているカラム名を受け取るためのバッファへのポインタとなる。この場合、以下に出てくるcchTextMax変数にバッファのサイズを指定しなければならない。
カラム名をセットする場合は、文字列の長さはわかるので、cchTextMax値は無視される。

cchTextMax 上記pszText変数のバイト数を指定する。この変数は、この構造体がカラム情報を受け取る時だけ使用される。セットする際にはこの値は無視される。
iSubItem

このカラムに関連のあるサブアイテムのインデックスを指定する。このカラムに関連のあるサブアイテムがどれかを明らかにする意味で使用される。
別に、カラムナンバーに一致しない値を指定することもできるし、リストビューコントロールはおかまいなしに動き続けるが、後でカラムナンバーを取得したいときに困るのでやめておいたほうがよい。

iImage イメージリスト内の0から始まるイメージインデックス。IE3.0以上がインストールされていなければならない。
iOrder 0から始まるカラムオフセット値。順番は左から右となっており、例えば0を指定すると一番左のカラムを表す。これもIE3.0以上がインストールされていなければならない。

リストビューコントロールを作成後、1つ以上のカラムを挿入しないといけない。ただし、レポートビューにしなければ必要はない。カラムを挿入するために、LV_COLUMN構造体作成し、カラムナンバーなど適当な値をセットする必要がある。その後、LVM_INSERTCOLUMNメッセージを送信する。

LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

上記のコードは概略を表したものだ。カラムヘッダテキストを記述し、LVM_INSERTCOLUMNメッセージを送信する。

●Items and subitems

アイテムはリストビューコントロール内のメイン要素である。レポートビュー以外の表示方法だと、リストビューコントロール内のアイテムだけしか見えないだろう。 サブアイテムはアイテムの詳細情報を保持している。アイテムは1つ以上のサブアイテムと連携する可能性があり、例えば、アイテムがファイル名だった場合、属性、サイズ、作成日時などをサブアイテムとして保持する。 レポートビューでは一番左にあるものがアイテムで、残りがサブアイテムである。これらはデータベースの1レコードとして考えることができ、さながらアイテムが主キーと言ったところか。

リストビューにアイテムを表示する場合、サブアイテムは必ず必要ではないが、よりユーザにわかりやすくするためにはあった方がいいだろう。 リストビューにアイテムを挿入するには、リストビューコントロールにLVM_INSERTITEMメッセージを送信し、 その際、lParamパラメータにはLV_ITEM構造体へのポインタを指定する必要がある。LV_ITEMは以下のように定義されている。

LV_ITEM STRUCT imask dd ? iItem dd ? iSubItem dd ? state dd ? stateMask dd ? pszText dd ? cchTextMax dd ? iImage dd ? lParam dd ? iIndent dd ? LV_ITEM ENDS

Field nameMeanings
imask この構造体のどのメンバ有効かを表す。上記LV_COLUMN構造体のimaskメンバと同様である。
iItem この構造体が参照する0から始まるアイテム番号で、テーブルの"行"ナンバーと考えることができる。
iSubItem

上記iItemで指定されたアイテムと関連のあるサブアイテムのインデックス値。この値はテーブルのカラムと考えることができる。例えば、新規に作成したリストビューコントロールにアイテムを挿入したければ、iItemの値は0になるだろう(このアイテムが一つ目なので)。
そして、iSubItemの値も0となる(このアイテムを1番目のカラムとしたいため)。このアイテムと関連のあるサブアイテムがあれば、iItemは関連付けたいアイテムのインデックス値となり(ここでは0となる)、iSubItemは1以上の値になる(どのカラムに挿入するかで異なる)。

例えば、リストビューコントロールに4つのカラムがあるとき、最初のカラムはアイテムで、残りの3つはサブアイテムとなる。4番目のカラムにサブアイテムを挿入したければ、iSubItemは3となる。

state アイテムのステータスを表す。アイテムの状態はユーザのオペレーションやプログラムにより変更しうるもので、フォーカス状態、ハイライト状態、選択状態がある。
状態フラグに加えて、1から始まるオーバレイイメージインデックス値、状態イメージインデックス値も含まれる。
stateMask 上記stateメンバに状態フラグ、オーバレイイメージインデックス値、状態イメージインデックス値が含まれているので、それらの値をセットしたいのか取り出したいのかをWindowsに伝える必要があるので、その際に使用する。
pszText アイテムを挿入する際にアイテム名として使用される文字列へのポインタ。アイテム属性を取得するためにこの構造体をセットする場合はアイテム名を保持するためのバッファへのポインタとなる。
cchTextMax アイテム情報を取得する際にだけ使用し、pszTextのバイトするを表す。
iImage リストビューコントロール用のアイコンを格納しているイメージリストのインデックス値。このアイテムが使用するアイコンを指定する。
lParam

リストビューコントロールをソートする際に使用するユーザが定義する値。アイテムをソートする際アイテムをペアにして比較するのだが、ペアとなっている両方のアイテムのlParam値が送信されるので、どちらが先かを決めればよい。

後ほどソートに関してより詳しい解説を行うので、ご安心あれ。

iIndent アイテムのインデント幅を指定する。IE3.0以上がインストールされていなければならない。

ではアイテム、サブアイテムをリストビューコントロールに挿入する際の手順をまとめてみよう。

  1. LV_ITEM構造体を作成する
  2. それに適切な値をセットする
  3. アイテムを挿入したい場合はLVM_INSERTITEMメッセージを送信する。サブアイテムの場合は、LVM_SETITEMである(SETとなっているので注意する)。これはアイテムとサブアイテムの関係を少しややこしくするかもしれないが、サブアイテムはアイテムの付加的な情報だと思えば理解できると思う。

●ListView Messages/Notifications

では、どうやってリストビューコントロールを作成するかを理解したところで、次はどのようにコミュニケーションをとるかを説明しよう。
リストビューコントロールと親ウィンドウはメッセージをやりとりすることによりコミュニケーションをとる。つまり、親ウィンドウはリストビューコントロールにメッセージを送信し、リストビューコントロールは親ウィンドウにWM_NOTIFYメッセージを送信しメッセージを受け取ったことを通知する。他のコモンコントロールと同じある。

●Sorting items/subitems

今度は先ほど言っていたソートに関する説明だ。CreateWindowEx関数をCALLする際にLVS_SORTASCENDINGLVS_SORTDESCENDINGを指定することにより、リストビューコントロールのデフォルトソート順を決められる。 これらの2つのスタイルはアイテム名しかソートしない。もし他の方法でソートしたければ、LVM_SORTITEMSメッセージをリストビューコントロールに送信する必要がある。

LVM_SORTITEMS
  • wParam = lParamSort
  • lParam = pCompareFunction

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

リストビューコントロールがLVM_SORTITEMSメッセージを受け取ると、2つのアイテムの比較結果を問い合わせるため、lParamに指定されている比較関数をCALLする。 つまり、比較関数により、2つのアイテムのうちどちらのアイテムが前に来るかがわかることになる。ルールは単純で、比較関数が負の値を返せば、1つ目のアイテム(lParam1)が前となり、正の値が返れば2つ目のアイテム(lParam2)が前となる。0が返れば同値ということになる。

この方法が上手く動くためには、LV_ITEM構造体のlParamの値が大事だ。(ユーザがカラムヘッダを押すなどして)アイテムをソートする際、lParamの値を使用してソートすることになる。
例では、lParamにアイテムのインデックス値をセットしており、LVM_GETITEMメッセージを送信することによりアイテム情報を取得している。アイテムが変更された際にはそれらのインデックス値も変わるので、ソートできなくならないように新しいインデックス値をlParamにセットしなおしてやる必要がある。

ユーザがカラムヘッダをクリックしたときにアイテムをソートしたければ、ウィンドウプロシージャでLVN_COLUMNCLICKメッセージを処理する必要がある。LVN_COLUMNCLICKWM_NOTIFYメッセージ経由で送信される。

Example:

この例は、リストビューコントロールを作成し、カレントフォルダのファイル名とファイルサイズをセットする。デフォルト表示はリポートビューだ。リポートビューではカラムヘッダをクリックするとアイテムが昇順/降順でソートされる。
表示形式はメニューから変更でき、アイテムをダブルクリックするとそのアイテム名をメッセージボックスで表示する。

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD IDM_MAINMENU equ 10000 IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red endm .data ClassName db "ListViewWinClass",0 AppName db "Testing a ListView Control",0 ListViewClassName db "SysListView32",0 Heading1 db "Filename",0 Heading2 db "Size",0 FileNamePattern db "*.*",0 FileNameSortOrder dd 0 SizeSortOrder dd 0 template db "%lu",0 .data? hInstance HINSTANCE ? hList dd ? hMenu dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, NULL mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,IDM_MAINMENU mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp InsertColumn proc LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc ret InsertColumn endp ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD invoke FindFirstFile,addr FileNamePattern,addr finddata .if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi .while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO? invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw invoke FindClose,FHandle .endif ret FillFileInfo endp String2Dword proc uses ecx edi edx esi String:DWORD LOCAL Result:DWORD mov Result,0 mov edi,String invoke lstrlen,String .while eax!=0 xor edx,edx mov dl,byte ptr [edi] sub dl,"0" mov esi,eax dec esi push eax mov eax,edx push ebx mov ebx,10 .while esi > 0 mul ebx dec esi .endw pop ebx add Result,eax pop eax inc edi dec eax .endw mov eax,Result ret String2Dword endp CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 .if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi .elseif SortType==2 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub eax,edi .elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer .else mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer,addr buffer1 .endif ret CompareFunc endp UpdatelParam proc uses edi LOCAL lvi:LV_ITEM invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0 mov edi,eax mov lvi.imask,LVIF_PARAM mov lvi.iSubItem,0 mov lvi.iItem,0 .while edi>0 push lvi.iItem pop lvi.lParam invoke SendMessage,hList, LVM_SETITEM,0,addr lvi inc lvi.iItem dec edi .endw ret UpdatelParam endp ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi invoke MessageBox,0, addr buffer,addr AppName,MB_OK ret ShowCurrentFocus endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL,\ LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax invoke InsertColumn invoke FillFileInfo RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED .elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK mov edx,wParam and edx,0FFFFh push edx or eax,edx invoke SetWindowLong,hList,GWL_STYLE,eax pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED .endif .elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList .if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW .if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2 invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,1 .else invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,2 .endif .else .if FileNameSortOrder==0 || FileNameSortOrder==4 invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,3 .else invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,4 .endif .endif assume edi:ptr NMHDR .elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif .endif pop edi .elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

Analysis:

一番初めにリストビューコントロールを作成するためにメインウィンドウを生成する。

.if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL,\ LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax

ウィンドウクラス名を"SysListView32"としてCreateWindowEx関数をCALLする。デフォルト表示はLVS_REPORTスタイルを指定してリポートビューとする。

invoke InsertColumn

リストビューコントロールの作成後、カラムを挿入する。

LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

ファイル名をセットするためにLV_COLUMN構造体の幅とラベルを使用することを宣言するために、imaskフラグにLVCF_TEXTLVCF_WIDTHをセットする必要がある。
その後、pszTextにはラベル名のアドレスを、lxにはカラム幅をピクセル値でセットする。準備が整ったらこのセットした構造体を渡すために、リストビューコントロールにLVM_INSERTCOLUMNメッセージを送信する。

or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT

最初のカラムを挿入し終えたら、次はファイルサイズをセットするためもう一つカラムを挿入する。サイズカラムは右並びにしたいので、fmtメンバにLVCFMT_RIGHTを指定する必要があり、そのためimaskLVCF_FMTフラグを指定する必要がでてくる。

mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

残りは簡単だ。pszTextにラベルへのポインタをセットし、lxに幅をセットする。そしてカラムナンバーとこの構造体のアドレスを指定して、LVM_INSERTCOLUMNメッセージをリストビューコントロールに送信する。

カラムを挿入する際、リストビューコントロールのアイテムをセットできる。

invoke FillFileInfo

FillFileInfoは以下のようになっている。

FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD invoke FindFirstFile,addr FileNamePattern,addr finddata

FIndFirstFile関数をCALLして、検索文字列に合致するファイル情報を取得する。FindFirstFile関数は以下のようなプロトタイプだ。

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

マッチするファイルが無かったらeaxレジスタにINVALID_HANDLE_VALUEがセットされ、見つかればFindNextFile関数をCALLする際に必要となるサーチハンドルを返す。

.if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi

ファイルがあれば、FHandleにセットし、アイテムのインデックス(行ナンバー)として使用するediレジスタを0にする。

.while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO?

ことチュートリアルでは、フォルダを扱いたくないので、dwFileAttributesFILE_ATTRIBUTE_DIRECTORYを含んでいるかチェックしてフォルダを除外している。

invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw

ShowFileInfo関数をCALLしてファイル名とファイルサイズをリストビューコントロールに挿入する。ediレジスタは現在の行ナンバーなのでインクリメントしておく。 最後にFindNextFile関数をCALLして次のファイルを検索する。FindNextFileが0を返すまで(つまり全部のファイルを検索し終わるまで)繰り返すことになる。

invoke FindClose,FHandle .endif ret FillFileInfo endp

全ファイル取得したら、サーチハンドルを閉じなければならない。

ではShowFileInfo関数をみてみよう。引数は2つで、アイテムインデックス(行ナンバー)とWIN32_FIND_DATA構造体へのポインタだ。

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA

ediレジスタにWIN32_FIND_DATA構造体のアドレスをセットする。

mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0

アイテムのラベルとlParam値をセットするので、imaskにLVIF_TEXTLVIF_PARAMフラグを指定する。次に、iItemに行ナンバーを、そしてこれがメインアイテムなのでSubItemに0をセットする。

lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam

次に、pszTextにラベル名をセットする。この場合、WIN32_FIND_DATA構造体のファイル名となる。後でソートを行う予定なので、lParamにソート時に必要となる値をセットしなければならない。ここでは行ナンバーをセットする。なので、インデックス値により値を取得できる。

invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

LV_ITEMに必要なデータをセットできたので、リストビューコントロールにLVM_INSERTITEMメッセージを送信して、この構造体を挿入する。

mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax

2番目のカラムにアイテムに関連するサブアイテムをセットする。サブアイテムはラベルだけなので、imaskにLVIF_TEXTを指定する。 そして、サブアイテムが存在すべきカラムを指定する。この際、iSubItemをインクリメントする。使用するラベルはファイルサイズだが、wsprintf関数をCALLして数字から文字列に変換しなければならない。 その後、pszTextにそのアドレスをセットする。

invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp

LV_ITEM構造体をセットし終えたら、リストビューコントロールにセットするためにLVM_SETITEMメッセージを送信する。LVM_INSERTITEMではなくLVM_SETITEMを使用することに注意しよう。なぜなら、サブアイテムはアイテムのプロパティとして考えられるからだ。なのでアイテムのプロパティをセットするのであって、新たにアイテムを挿入するのではない。

全アイテムがリストビューコントロールに挿入されれば、背景色とテキストをセットする。

RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

ここではRGBマクロを使用して、赤、緑、青の各値をeaxレジスタにセットしている。LVM_SETTEXTCOLORLVM_SETTEXTDBCOLORメッセージにより文字列の前景、背景色をセットする。 リストビューコントロールの背景色はLVM_SETBKCOLORメッセージを送信する。

invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

メニューからユーザが表示形式を選択できるようにするために、メニューハンドルを取得する。ユーザが現在の表示形式を知るためにメニューにラジオボタンを表示する。現在の表示形式を表すためのラジオボタンなので、CheckMenuRadioItem関数をCALLして、メニューアイテムより先にラジオボタンをセットする必要がある。

リストビューコントロールの幅と高さを0にして作成していることに注意しよう。親ウィンドウがリサイズする際にリストビューコントロールもリサイズすることになる。なのでその時に親ウィンドウと同じサイズでサイズ調整すればよい。
この例ではリストビューコントロールは親ウィンドウのクライアントエリア全域になっている。

.elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

親ウィンドウがWM_SIZEメッセージを受け取るとき、lParamの下位ワードはクライアントエリアの幅、上位ワードは高さとなる。その後、MoveWindow関数により親ウィンドウのクライアントエリア全体をリストビューコントロールしてリサイズする。

ユーザがメニューを選択すると、リストビューコントロールを選択された表示形式に変更する。これはSetWindowLong関数を使用して新しいスタイルをセットする。

.elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK

まずは現在のリストビューコントロールの表示形式を取得し、前の表示形式をクリアする。LVS_TYPEMASKは4つのビュースタイル(LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT)をあわせた定数値だ。
なので、現在の表示形式フラグと"not LVS_TYPEMASK"とのandをとる。これは結局現在の表示形式をクリアすることになる。

メニューを作成する際、少し工夫をしており、メニューIDに表示形式定数を使用している。

IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT

なので、親ウィンドウがWM_COMMANDメッセージを受け取ると、メニューIDとなるwParamの下位ワードが要求された表示形式となる。

mov edx,wParam and edx,0FFFFh

wParamの下位ワードは表示形式なので、上位ワードを0クリアする。

push edx or eax,edx

そして、要求された表示形式を現在のスタイルにプラスする。

invoke SetWindowLong,hList,GWL_STYLE,eax

SetWindowLong関数により新しいスタイルをセットする。

pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED .endif

選択されたメニューアイテムの前にラジオボタンをセットする必要がある。なので、現在の表示形式を指定して、CheckMenuRadioItem関数をCALLする。

リポートビューのカラムヘッダをクリックされたときに、リストビューコントロール内のアイテムをソートしたいので、WM_NOTIFYメッセージに対して処理を行う。

.elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList

WM_NOTIFYメッセージを受け取ると、lParamはNMHDR構造体へのポインタとなっている。 このメッセージがリストビューコントロールから送信されたメッセージかどうかはNMHDR構造体のhwndFromメンバがリストビューコントロールのハンドルと同じかどうかでチェックできる。 もしマッチすれば、このメッセージはリストビューコントロールからのものであると仮定できる。

.if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW

リストビューコントロールからの通知であれば、codeメンバがLVN_COLUMNCLICKかどうかチェックし、もしそうならカラムヘッダがクリックされたということである。 LVN_COLUMNCLICKだったら、lParamがNMHDR構造体のスーパーセットであるNM_LISTVIEW構造体へのポインタとなる。
その後、ユーザがどのカラムヘッダをクリックしたかを調査するため、iSubItemメンバがいくつかを比較する。

.if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2

iSubItemが1なら、2番目のカラム、つまりファイルサイズカラムがクリックされたということである。ソート状態を表す変数を使用し、0ならまだソートしていない、1なら昇順ソート済み、2なら降順ソート済みであると表すことにする。 アイテム/サブアイテムが以前に降順でソートされていれば、昇順でソートしなおすことにする。

invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

wParamを1にし、lParamに比較関数へのポインタを指定して、LVM_SORTITEMSメッセージをリストビューコントロールに送信する。wParamの値はユーザ定義でなので、好きな値を指定できる。この例ではソート方法を表すことにしている。
では比較関数を見てみよう。

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256

比較関数は、リストビューコントロールがCALLすることになるのだが、その際LV_ITEM構造体のlParamの値をlParam1とlParam2にセットされている。lParamにはアイテムのインデックス値をセットしたことを思い出そう。 なので、インデックス値を使用してリストビューコントロールにクエリーを出せばアイテムに関する情報が取得できる。取得した情報はアイテム/サブアイテムのラベル名なので、LV_ITEM構造体のimaskメンバをLVIF_TEXTにし、pszTextcchTextMaxをバッファのアドレス、バッファのサイズとする。

.if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

SortTypeの値が1か2ならカラムサイズがクリックされたことになる。1は昇順でソートしたことを2ならその逆を意味するので、iSubItemを1として(ファイルサイズのカラムナンバー)リストビューコントロールにLVM_GETITEMTEXTメッセージを送信し、サブアイテムのラベル名(ここではファイルサイズ文字列)を取得する。

invoke String2Dword,addr buffer mov edi,eax

今回私が作成したString2Dword関数により、サイズ文字列をdword値に変換する。dword値がeaxレジスタに返ってくるので、後で使用するためediレジスタにセットする。

invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi

lParam2も同様にして、2つのファイルサイズ値を取得する。その後それらを比較するのだが、そのルールは以下の通りだ。

この場合、昇順でアイテムをソートしたいので、単純に1番目の値から2番目の値を引き算し、結果をeaxレジスタに格納すればよいことになる。

.elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer

ユーザがファイル名カラムをクリックした際、ファイル名を比較するのだが、まずはファイル名を取得してその後lstrcmpi関数により比較する。lstrcmpi関数からの戻り値をそのままreturnする。 というのも同じ比較方法を使用するからだ。つまり、負数なら1番目の引数が2番目の引数より小さいことになる。

アイテムをソートする際、UpdatelParam関数をCALLして新しいインデックス値lParamに反映させる必要がある。

invoke UpdatelParam mov SizeSortOrder,1

この関数は単にリストビューコントロール内の全アイテムを取得し、lParamを新しいインデックス値に更新する。これをしておかなければ、lParamがアイテムのインデックス値として仮定しているのでソートが上手くできなくなってしまう。

.elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif

ユーザがアイテムをダブルクリックしたとき、そのアイテムのラベル名をメッセージボックスで出力したいので、NMHDRのcodeメンバがNM_DBLCLKかどうかをチェックする必要がある。
ダブルクリックされていれば、そのアイテムのラベル名を取得し、メッセージボックスで出力する。

ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

どのアイテムがダブルクリックされたかをどうやって取得すればいいのか。アイテムがクリックかダブルクリックされたとき、そのアイテムは"フォーカス"状態になる。 複数のアイテムがハイライトしていても(選択されていたとしても)、それらのうちの1つしかフォーカス状態にはならない。なのでそのフォーカス状態となっているアイテムを見つけるのがここでやるべきことだ。
そのためには、リストビューコントロールにLVM_GETNEXTITEMメッセージを送信する。このとき、lParamには検索したい状態を指定し、wParamは全アイテムを検索対象にするという意味で -1とする。 eaxレジスタに見つかったアイテムインデックス値が返ることになる。

mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

その後、リストビューコントロールにLVM_GETITEMメッセージを送信し、ラベル名を取得する。

invoke MessageBox,0, addr buffer,addr AppName,MB_OK

これでやっと、メッセージボックスにラベルを表示できる。

リストビューコントロールにアイコンを使用したければ、ツリービューコントロールのチュートリアルを読めばわかるだろう。手順はほとんど同じだ。



[戻る]