山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他ツリービュー(32/64bit)
概要
ツリービューのサンプルである。
ツリービューへの項目(アイテム)の追加、名前変更、削除、ドラッグによる移動、右クリックメニューによる左記動作の実行を行う。実行ファイル作成には、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