概要

ツリービューのサンプルである。
ツリービューへの項目(アイテム)の追加、名前変更、削除、ドラッグによる移動、右クリックメニューによる左記動作の実行を行う。実行ファイル作成には、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