ウィンドウズプログラミング講座第11回:ツリービューコントロール
概要
ツリービュークラスにツリービューコントロールを実装します。
名前がいっしょなのでややこしいけど、ツリービュークラスはツリービューコントロールの親ウィンドウです。
ツリービューコントロールは自分でスクロールしてくれるんで、本当はビュークラスで管理する必要もないんだけど、何でもかんでもアプリケーションクラスに入れちゃうとソースが複雑になるので分けました。
ツリービュークラス
変数
class TreeView : public View { HWND hTree;
ツリービューコントロールハンドルを追加しますた。
追加関数
VOID CreateTree(); INT CreateChild( HTREEITEM hParent ); VOID MakeFullPath( LPTSTR path, HTREEITEM hItem, BOOL first = TRUE ); LRESULT OnTvnGetDispInfo(); };
CreateTree() 関数でツリーを作成します。
CreateChild() 関数でサブディレクトリ内のツリーを作成します。
MakeFullPath() 関数で指定項目のフルパス文字列を作成します。
OnTvnGetDispInfo() はまだ表示されてない項目を作成しろというツリービューからの通知対応処理です。
ツリー作成
クラス初期化時にツリービューコントロールと最初のツリー項目を作成します。
BOOL TreeView :: Init( Window * parent, UINT id ) { dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS; hTree = CreateWindow( WC_TREEVIEW, NULL, dwStyle, 0, 0, 0, 0, hwnd, ( HMENU )( UINT_PTR ) idTreeCtrl, GetModuleHandle( NULL ), NULL ); CreateTree(); } VOID TreeView :: CreateTree() { TVINSERTSTRUCT tvIns; TCHAR drives[ _MAX_PATH ]; TCHAR fname[ _MAX_PATH ]; TreeView_DeleteAllItems( hTree ); ZeroMemory( & tvIns, sizeof tvIns ); tvIns.hParent = TVI_ROOT; tvIns.hInsertAfter = TVI_LAST; tvIns.item.mask = TVIF_TEXT | TVIF_STATE; tvIns.item.pszText = "Root"; tvIns.item.state = tvIns.item.stateMask = TVIS_BOLD | TVIS_EXPANDED; tvIns.hParent = TreeView_InsertItem( hTree, & tvIns ); tvIns.item.mask = TVIF_TEXT | TVIF_PARAM; tvIns.item.pszText = fname; tvIns.item.lParam = FILE_ATTRIBUTE_DIRECTORY; GetLogicalDriveStrings( sizeof ( drives ), drives ); for ( LPTSTR s = drives; ( * s ) != '\0'; s += strlen( s ) + 1 ) { strcpy( fname, s ); fname[ strlen( fname ) - 1 ] = '\0'; CreateChild( TreeView_InsertItem( hTree, & tvIns ) ); } }
初期のツリーで表示されるのはドライブ名のみです。
CreateChild() 関数でドライブ内の項目も作成していますが、これを表示するとさらにサブフォルダ下の項目も作成しなければならないので非表示にしました。
ドライブを展開するとイベントが発生するのでそのとき作られます。
子アイテム作成
hParent で親となるアイテム(=フォルダ)を指定するとその中身のアイテムツリーを作成します。
INT TreeView :: CreateChild( HTREEITEM hParent ) { TCHAR path[ _MAX_PATH ]; WIN32_FIND_DATA ffd; HANDLE hFind; TVINSERTSTRUCT tvIns; INT count; MakeFullPath( path, hParent ); ZeroMemory( & ffd, sizeof ffd ); strcat( path, "\\*.*" ); hFind = FindFirstFile( path, & ffd ); count = 0; if ( hFind != INVALID_HANDLE_VALUE ) { ZeroMemory( & tvIns, sizeof tvIns ); tvIns.hParent = hParent; tvIns.hInsertAfter = TVI_LAST; tvIns.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_CHILDREN; for ( ;; ) { if ( strcmp( ffd.cFileName, "." ) != 0 && strcmp( ffd.cFileName, ".." ) != 0 ) { tvIns.item.pszText = ffd.cFileName; tvIns.item.lParam = ffd.dwFileAttributes; if ( ( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 ) { tvIns.item.cChildren = I_CHILDRENCALLBACK; } TreeView_InsertItem( hTree, & tvIns ); count++; } if ( ! FindNextFile( hFind, & ffd ) ) { break; } } FindClose( hFind ); } return count; }
最初に呼ばれるのはドライブ名です。ドライブ名もフォルダですから。
もしディレクトリを見つけた場合は tvIns.item.cChildren = I_CHILDRENCALLBACK をしてるので表示が必要になったときイベントが発生して作成されます。
そのときまたここが呼ばれます。
フルパス文字列作成
指定された項目のフルパス名を作成します。
VOID TreeView :: MakeFullPath( LPTSTR path, HTREEITEM hItem, BOOL first ) { HTREEITEM hParent = TreeView_GetParent( hTree, hItem ); TCHAR fname[ _MAX_FNAME ]; TVITEMEX tvItem; if ( first ) { ZeroMemory( path, sizeof path ); } if ( hParent != NULL ) { ZeroMemory( & tvItem, sizeof tvItem ); tvItem.hItem = hItem; tvItem.pszText = fname; tvItem.cchTextMax = sizeof fname; tvItem.mask = TVIF_HANDLE | TVIF_TEXT; TreeView_GetItem( hTree, & tvItem ); if ( first ) { if ( fname[ strlen( fname ) - 1 ] == '\\' ) { fname[ strlen( fname ) - 1 ] = '\0'; } } else { strcat( fname, "\\" ); } MoveMemory( path + strlen( fname ), path, strlen( path ) + 1 ); CopyMemory( path, fname, strlen( fname ) ); MakeFullPath( path, hParent, FALSE ); } }
親をたどれば親フォルダ名が分かるのでルートに達するまで親のアイテム名を先頭に足し続ければ完成です。
TVN_GETDISPINFO 通知処理
まだ作成されていないフォルダの中身を作成します。
LRESULT TreeView :: OnTvnGetDispInfo() { TVITEM tvItem; ZeroMemory( & tvItem, sizeof tvItem ); tvItem.mask = TVIF_HANDLE | TVIF_CHILDREN; tvItem.hItem = lpnmtvdi->item.hItem; if ( CreateChild( lpnmtvdi->item.hItem ) > 0 ) { tvItem.cChildren = 1; } TreeView_SetItem( hTree, & tvItem ); InvalidateRect( hTree, NULL, FALSE ); return 0; }
CreateChild() 関数でフォルダの中身を作成します。
tvItem.cChildren = 1; で作成済みであることを設定します。これが 0 なら空のフォルダです。