ツリービューのサンプルである。
ツリービューへの項目(アイテム)の追加、名前変更、削除、ドラッグによる移動、右クリックメニューによる左記動作の実行を行う。実行ファイル作成には、comctl32.libをリンクする必要があります。

項目の追加

ダイアログボックスの初期化時にTreeView_InsertItemマクロによって追加する。

名前変更

 プッシュボタンより実施する場合は、TreeView_GetSelectionで選択中のハンドルを取得し、TreeView_EditLabelでエディットボックスを表示する。
 ReturnキーやESCキーを押すとダイアログボックスにメッセージが送られダイアログボックスが終了してしまうので、名前が変更する前に発生するWM_NOTIFYメッセージの TVN_BEGINLABELEDITでエディットボックスのウィンドウプロシージャーをサブクラス化(標準のウィンドウプロシージャーに代わって自作のプロシージャーEditProcを実行させる)する。EditProcが呼ばれたときのWM_GETDLGCODEメッセージでDLGC_WANTALLKEYSを返すことにより制御キーを無視させる。

削除

 プッシュボタンより実施する場合は、TreeView_GetSelectionで選択中のハンドルを取得しTreeView_DeleteItemで削除する。DELキーの場合は、あらかじめサブクラス化した、ツリービューのプロシージャーTreeViewProcでWM_KEYDOWNメッセージが発生しwParamがVK_DELETEの場合、ダイアログボックスにWM_TREEVIEW_DELETEメッセージを送信する。

ドラッグによる移動

 ドラッグが開始されると、WM_NOTIFYメッセージTVN_BEGINDRAGが発生するのでflag変数にドラッグ中であることを保存する。メッセージには現在のアイテムが保存されているのでTreeView_SelectItemマクロで選択中にする。
 マウスが移動中を検出するために、ツリービューのサブクラス化されたプロシージャーTreeViewProcでWM_MOUSEMOVEメッセージを捕捉しダイアログボックスにWM_TREEVIEW_MOUSEMOVメッセージを送信する。
ダイアログボックスでは、ドラッグ中の場合、マウス座標よりTreeView_HitTestマクロでアイテムを取得しTreeView_SelectItemマクロで選択状態にする。ドラッグが終了するとツリービューでWM_LBUTTONUPが発生するのでツリービューのプロシージャーTreeViewProcで捕捉しダイアログボックスにWM_TREEVIEW_LBUTTONUPを送信する。
マウス座標よりTreeView_HitTestで移動後のアイテムを取得する。アイテムの移動は、まず移動元が移動先に含まれていないかtree_dup_chk関数でチェックする。TreeView_GetItemマクロでアイテムを取得し、TreeViewData構造体に保存、TreeView_GetChildマクロで子アイテムがあるかチェックをしておく。TreeView_GetNextSiblingマクロで次のアイテムを取得する。TreeView_GetChildマクロ実行後にすぐtree_dup_chk関数を再帰的に読み出すと、正常に動作しないので兄弟アイテムをTreeViewData構造体に保存してから再度TreeViewData構造体で検索して子アイテムを検索する。
移動は、コピー後に移動元を削除する。

 

 

treeview.cpp

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <tchar.h>
#include "resource.h"

HINSTANCE hInst;

HWND parent_hDlg;	//	ツリービューの親ダイアログボックス
WNDPROC default_tree_view_proc;	//	サブクラス化する前のツリービュープロシージャー
WNDPROC default_edit_proc;	//	サブクラス化する前のツリービューエディットボックスプロシージャー

//	ツリービューの項目管理構造体

struct TreeViewData{
	HTREEITEM h;	//	アイテムハンドル	
	TCHAR* str;		//	アイテム文字列
	TreeViewData* next;	//	次のアイテム
	TreeViewData* child;	//	子アイテム
	TreeViewData(){
		next=child=0;
	}
};

LRESULT CALLBACK    TreeViewProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); //  サブクラス化を行うためのツリービューウインドウプロシージャ
LRESULT CALLBACK    EditProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam); //  サブクラス化を行うためのツリービューエディットボックスウインドウプロシージャ
TreeViewData* tree_dup_chk(HWND tH,HTREEITEM dtc,HTREEITEM src,TreeViewData* top,int* f);//	ツリービューのsrcの中に(子も含めて)にdtcが含まれていないかチェック
void tree_copy(HWND tH,TreeViewData* top,HTREEITEM dtc,bool f=false);	//	ツリービューのsrcをdtcへコピー
void TreeViewDataDelete(TreeViewData* top);		//	TreeView構造体を削除
void tree_move(HWND tH,HTREEITEM dtc,HTREEITEM src,TreeViewData* p);	//	ツリービューのsrcをdtcへ移動
LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);	//	ダイアログボックスプロシージャー


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,LPSTR lpsCmdLine, int nCmdShow){
	hInst=hCurInst;
	InitCommonControls();
	DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc);
	return (int)0;
}


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

LRESULT CALLBACK    TreeViewProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){
	switch(msg){
	case WM_KEYDOWN:
		switch(wParam){
			case VK_DELETE:	//	DELキーが押されたらダイアログボックスにメッセージを送信
				SendMessage(parent_hDlg,WM_TREEVIEW_DELETE,wParam,lParam);
				break;
		}
		break;
	case WM_MOUSEMOVE:	//	マウス移動中
		SendMessage(parent_hDlg,WM_TREEVIEW_MOUSEMOVE,wParam,lParam);
		break;
	case WM_LBUTTONUP:	//	マウス右ボタンが離された
		SendMessage(parent_hDlg,WM_TREEVIEW_LBUTTONUP,wParam,lParam);
		break;
	}
    return  CallWindowProc((WNDPROC)default_tree_view_proc,hWnd,msg,wParam,lParam);
}

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

LRESULT CALLBACK    EditProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam){
	switch(msg){
	case WM_GETDLGCODE:
		return DLGC_WANTALLKEYS;	// すべての制御キーをエディットボックスで処理をしたことを示す
	}
    return  CallWindowProc((WNDPROC)default_edit_proc,hWnd,msg,wParam,lParam);
}


//	ツリービューのsrcの中に(子も含めて)にdtcが含まれていないかチェック 含まれている場合は*fに0をセットして終了
//	検索時にsrcにおけるアイテムのリストを作成

TreeViewData* tree_dup_chk(HWND tH,HTREEITEM dtc,HTREEITEM src,TreeViewData* top,int* f){
	HTREEITEM s=src;
	HTREEITEM c;
	TVITEM to;
	TCHAR buf[64];
	TreeViewData* p=top;
	TreeViewData* tmp;
	int n=0;
	while(s){	//	兄弟アイテムのチェック
		to.mask=TVIF_TEXT | TVIF_HANDLE;
		to.hItem=s;
		to.pszText=buf;
		to.cchTextMax=sizeof(buf)/sizeof(TCHAR);
		TreeView_GetItem(tH,&to);	//	アイテムの取得
		if(p==0){
			p=new TreeViewData;
		}else{
			if(n){
				tmp=new TreeViewData;
				p->next=tmp;
				p=tmp;
			}
		}
		++n;
		p->str=_tcsdup(buf);
		p->h=s;
		if(s==dtc){
			*f=0;
			return top;
		}
		c=TreeView_GetChild(tH,s);	//	子アイテムのハンドルを取得

		if(c){
			TreeViewData* child;
			child=new TreeViewData;
			child->str=_tcsdup(buf);
			child->h=c;
			p->child=child;
		}
		if(top==0){
			top=p;
			break;
		}
		s=TreeView_GetNextSibling(tH,s);
	}

	p=top;
	while(p){	//	子アイテムのチェック
		if(p->child){
			tree_dup_chk(tH , dtc , p->child->h , p->child,f);
			if(*f==0)
				return top;
		}
		p=p->next;
	}
	return top;	
}

//	ツリービューのsrcをdtcへコピー
//	fがtrue:dtcの子として作成,fがfalse:dtcの兄弟として作成

void tree_copy(HWND tH,TreeViewData* top,HTREEITEM dtc,bool f){
	TreeViewData* p=top;
	TV_INSERTSTRUCT tvi;
	HTREEITEM d=dtc;
	HTREEITEM t1;
	int n=0;

	while(p){
		if((n==0 && f==true) || n){	// dtcの子供として作成
			tvi.hInsertAfter=TVI_LAST;
			tvi.item.mask=TVIF_TEXT;
			tvi.hParent=dtc;
		}else{	//	dtcの兄弟として作成
			HTREEITEM parentH = TreeView_GetParent(tH, dtc);
			tvi.hInsertAfter=dtc;
			tvi.item.mask=TVIF_TEXT;
			tvi.hParent=parentH;
		}
		tvi.item.pszText=p->str;
		t1=TreeView_InsertItem(tH,&tvi);	//	アイテムの作成
		
		if(p->child){
			tree_copy(tH,p->child,t1,true);
		}
		++n;
		p=p->next;
	}
}

//	TreeView構造体を削除

void TreeViewDataDelete(TreeViewData* top){
	TreeViewData* p=top;
	TreeViewData* tmp;
	while(p){
		if(p->child)
			TreeViewDataDelete(p->child);
		tmp=p;
		p=p->next;
		free(tmp->str);
		delete tmp; 
	}
}

//	ツリービューのsrcをdtcへ移動

void tree_move(HWND tH,HTREEITEM dtc,HTREEITEM src,TreeViewData* p){
//	srcの中に(子も含めて)にdtcが含まれていないかチェック 含まれている場合は移動せずに終了
	TreeViewData* top=0;
	int f;
	top=tree_dup_chk(tH,dtc,src,p,&f);
	if(f==0){
		MessageBox(tH,TEXT("コピー元がコピー先の一部となっているため移動できません"),TEXT("エラー"),MB_OK);
		return;
	}
//	srcからdtcへコピー

	HTREEITEM	c=TreeView_GetChild(tH,dtc);
	if(c){
		if(MessageBox(tH,TEXT("子供として貼り付けますか"),TEXT("メッセージ"),MB_YESNO)==IDYES)
			tree_copy(tH,top,dtc,true);
		else
			tree_copy(tH,top,dtc,false);
	}else
		tree_copy(tH,top,dtc,false);

	TreeViewDataDelete(top);
	TreeView_EnsureVisible(tH,dtc);
	TreeView_DeleteItem(tH, src);
}

//	ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	static HWND tH;	//	ツリービューハンドル
	static int flag;	//	ドラッグ中の場合TVN_BEGINDRAGがセット
	static HTREEITEM start;	//	移動元ハンドル
	static HWND editH;	//	エディットボックスハンドル
	static HMENU hMenu, hSub;	//	ポップアップメニューのハンドル
	LONG_PTR err;
	HTREEITEM t1,t2,t4;
	TV_INSERTSTRUCT tvi;
	switch (msg) {
        case WM_INITDIALOG:
			parent_hDlg=hDlg;
			editH=0;
			start=0;
			hMenu = LoadMenu(hInst,TEXT("RMENU"));	//	ポップアップメニューのハンドルを取得
			hSub = GetSubMenu(hMenu, 0);

			tH=GetDlgItem(hDlg,IDC_TREEVIEW100);
			default_tree_view_proc=(WNDPROC)GetWindowLongPtr(tH,GWLP_WNDPROC);
			err=SetWindowLongPtr(tH,GWLP_WNDPROC,(LONG_PTR)TreeViewProc);	//	ツリービューのサブクラス化

			flag=0;
			TV_INSERTSTRUCT tv;
			memset(&tv,0,sizeof(tv));
			tv.hInsertAfter=TVI_LAST;
			tv.item.mask=TVIF_TEXT;
			tv.hParent=TVI_ROOT;
			tv.item.pszText=TEXT("家康");
			t1=TreeView_InsertItem(tH,&tv);
			tv.hParent=t1;
			tv.item.pszText=TEXT("秀忠");
			t4=TreeView_InsertItem(tH,&tv);
			tv.hParent=t4;
			tv.item.pszText=TEXT("家光");
			t4=TreeView_InsertItem(tH,&tv);

			tv.hInsertAfter=TVI_LAST;
			tv.item.mask=TVIF_TEXT;
			tv.hParent=TVI_ROOT;
			tv.item.pszText=TEXT("秀吉");
			t1=TreeView_InsertItem(tH,&tv);
			tv.hParent=t1;
			tv.item.pszText=TEXT("秀頼");
			t4=TreeView_InsertItem(tH,&tv);

			tv.hInsertAfter=TVI_LAST;
			tv.item.mask=TVIF_TEXT;
			tv.hParent=TVI_ROOT;
			tv.item.pszText=TEXT("信長");
			t1=TreeView_InsertItem(tH,&tv);
			tv.hParent=t1;
			tv.item.pszText=TEXT("信孝");
			t4=TreeView_InsertItem(tH,&tv);
			return TRUE;
		case WM_NOTIFY:
			if(wParam == IDC_TREEVIEW100){
				LPNMHDR lpnmhdr;
				lpnmhdr=(LPNMHDR)lParam;
				switch(lpnmhdr->code){
				case TVN_BEGINDRAG:		//	ドラッグ開始
					NM_TREEVIEW* pnmtv=(NM_TREEVIEW*)lParam;
					start=pnmtv->itemNew.hItem;
					flag=TVN_BEGINDRAG;
					TreeView_SelectItem(tH, start);
					break;
				}
				TV_DISPINFO *ptvdisp=(TV_DISPINFO *)lParam;
				switch(ptvdisp->hdr.code){
					case TVN_BEGINLABELEDIT:	//	アイテムの編集開始
						editH=(HWND)SendMessage(tH,TVM_GETEDITCONTROL,0,0);
						default_edit_proc=(WNDPROC)SetWindowLongPtr(editH,GWLP_WNDPROC,(LONG_PTR)&EditProc);	//	Returnキー ESCキーの処理のためにサブクラス化
					case TVN_ENDLABELEDIT:	//	アイテムの編集が終了
						TreeView_SetItem(tH , &ptvdisp->item);	//	編集結果を反映
						break;
				}
				break;
			}
			break;
		case WM_TREEVIEW_MOUSEMOVE:	//	ツリービュー内でマウスカーソルが移動した場合
			if(flag==TVN_BEGINDRAG){ // ドラッグ中
				HTREEITEM end;

				TVHITTESTINFO thi;
				thi.flags=TVHT_ONITEM;

				thi.pt.x = (short int)LOWORD(lParam);	//マルチディスプレイの場合、スクリーン座標が負になる場合がある
				thi.pt.y = (short int)HIWORD(lParam);	//  プライマリディスプレイを右側にした場合の、左側のセカンダリディスプレイのスクリーン座標など
				
				if((end=TreeView_HitTest(tH,&thi))!=0){
					TreeView_SelectItem(tH, end);			//	カーソル位置のアイテムを選択状態にする
				}
			}
			break;
		case WM_TREEVIEW_DELETE:	//	ツリービュー内でDELキーが押された場合
			SendMessage(hDlg,WM_COMMAND,IDC_DELITEM,0);
			break;
		case WM_TREEVIEW_LBUTTONUP:	//	ツリービューでマウス左ボタンがUPされた場合
			if(flag==TVN_BEGINDRAG){	//	ドラックが終了した場合
				flag=0;
				HTREEITEM end;
				TVHITTESTINFO thi;
				thi.flags=TVHT_ONITEM;

				thi.pt.x = (short int)LOWORD(lParam);	//マルチディスプレイの場合、スクリーン座標が負になる場合がある
				thi.pt.y = (short int)HIWORD(lParam);	//  プライマリディスプレイを右側にした場合の、左側のセカンダリディスプレイのスクリーン座標など
				
				if((end=TreeView_HitTest(tH,&thi))!=0){

					TVITEM ti,to;
					TCHAR buf[64];
					ti.mask=TVIF_TEXT;
					ti.hItem=end;
					ti.pszText=buf;
					ti.cchTextMax=sizeof(buf)/sizeof(TCHAR);
					if(TreeView_GetItem(tH,&ti)){
						TCHAR buf2[64];
						to.mask=TVIF_TEXT;
						to.hItem=start;
						to.pszText=buf2;
						to.cchTextMax=sizeof(buf2)/sizeof(TCHAR);
						TreeView_GetItem(tH,&to);

						tree_move(tH,end,start,0);
						TreeView_SetItemState(tH,end, 0,0x000f);
					}
				}
			}
			break;
		case WM_CONTEXTMENU:	//	ダイアログ内で右クリックされた場合
			{
				TVHITTESTINFO thi;
				thi.flags=TVHT_ONITEM;
				int x,y;

				x=thi.pt.x = (short int)LOWORD(lParam);	//マルチディスプレイの場合、スクリーン座標が負になる場合がある
				y=thi.pt.y = (short int)HIWORD(lParam);	//  プライマリディスプレイを右側にした場合の、左側のセカンダリディスプレイのスクリーン座標など

				ScreenToClient(tH, &thi.pt);	//	スクリーン座標をツリービューの座標に変換

				if((t1=TreeView_HitTest(tH,&thi))!=0){
					TreeView_SelectItem(tH, t1);
					TrackPopupMenu(hSub,TPM_CENTERALIGN,x,y,0,hDlg,	NULL);	//	ポップアップメニューの表示
				}
				break;
			}
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
				case IDC_NEWITEM:	//	アイテムの新規作成
					t1=TreeView_GetSelection(tH);
					if(t1){
						if(MessageBox(tH,TEXT("子供として作成しますか"),TEXT("メッセージ"),MB_YESNO)==IDYES){
							tvi.hInsertAfter=TVI_LAST;
							tvi.item.mask=TVIF_TEXT;
							tvi.hParent=t1;
							tvi.item.pszText=TEXT("新しいアイテム");
							t1=TreeView_InsertItem(tH,&tvi);
							TreeView_EnsureVisible(tH,t1);
						}else{
							t2=TreeView_GetParent(tH,t1);
							tvi.hInsertAfter=t1;
							tvi.item.mask=TVIF_TEXT;
							tvi.hParent=t2;
							tvi.item.pszText=TEXT("新しいアイテム");
							t1=TreeView_InsertItem(tH,&tvi);
							TreeView_EnsureVisible(tH,t1);
						}
					}
					break;
				case IDC_RENAMEITEM:	//	アイテムの名前変更
					t1=TreeView_GetSelection(tH);
					if(tH)
						TreeView_EditLabel(tH,t1);
					break;
				case IDC_DELITEM:	//	アイテムの削除
					t1=TreeView_GetSelection(tH);
					if(tH)
						TreeView_DeleteItem(tH,t1);
					break;
				case IDC_EXIT:	//	ダイアログの終了
					EndDialog(hDlg, TRUE);
					return TRUE;
				default:
					return FALSE;

			}
			default:
				return FALSE;
	}
	return TRUE;
}

resource.h

#define IDC_TREEVIEW100 100
#define IDC_NEWITEM	101
#define IDC_RENAMEITEM	103
#define IDC_DELITEM	104
#define IDC_EXIT	105

#define WM_TREEVIEW_LBUTTONUP	(WM_USER+1)
#define WM_TREEVIEW_DELETE	(WM_USER+2)
#define WM_TREEVIEW_MOUSEMOVE	(WM_USER+3)



treeview.rc

#include "windows.h"
#include "resource.h"

DLG1 DIALOG DISCARDABLE 0, 0, 334, 300
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP  | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT
CAPTION "ツリービューサンプル"
FONT 8, "MS 明朝"
{
 CONTROL "TreeView100", IDC_TREEVIEW100, "SYSTREEVIEW32", WS_TABSTOP | WS_CHILD | WS_VISIBLE | WS_BORDER | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_EDITLABELS, 19, 14, 296, 241
 CONTROL "新しいアイテム(&N)", IDC_NEWITEM, "BUTTON", WS_TABSTOP |WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 6, 272, 75, 20
 CONTROL "名前の変更(&R)", IDC_RENAMEITEM, "BUTTON",WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 88, 272, 75, 19
 CONTROL "削除(&D)", IDC_DELITEM, "BUTTON", WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 170, 272, 75, 19
 CONTROL "閉じる(&C)", IDC_EXIT, "BUTTON", WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 252, 272, 75, 19
}

RMENU	MENU
BEGIN
	POPUP	""
	BEGIN
		MENUITEM	"新しいアイテム(&N)",IDC_NEWITEM
		MENUITEM	"名前の変更(&R)",IDC_RENAMEITEM
		MENUITEM	"削除(&D)", IDC_DELITEM
	END
END