第115章 ツリービューにメニューをつける


前回のプログラムに何か不満はありませんか? 項目を選択した後メニューに戻っていちいち「編集」 「項目の削除」などというのは面倒くさいですね。 それよりも、選択したい項目を右クリックすると その場にメニューが出るとありがたいですね。 これは、すでに第44章 でやりました。TrackPopupMenu関数を使えばよいですね。 さて、右クリックして出てくるメニュー項目は 「項目の編集」「項目の削除」「項目の追加」があれば よいわけですから、これはクラスメニューの「編集」の サブ項目を利用すれば手間がかかりません。 ただし、すでにロードされているのでLoadMenu関数を 呼ぶ必要はありません。従ってDestroyMenu関数も必要ありません。 (DestroyMenu関数を呼ぶとクラスメニューがおかしくなります)

さて、ツリービューが右クリックされたのはどうやって知ればよいでしょうか? ウィンドウプロシージャでWM_RBUTTONDOWNを待っていてもだめです。 親ウィンドウのクライアント領域にはツリービューコントロールが 貼り付けてあります。
ツリービューをサブクラス化するのかな? もっと簡単な方法があります。ツリービューを右クリックすると 親ウィンドウにはWM_CONTEXTMENUメッセージが 来ます。

WM_CONTEXTMENU hwnd = (HWND) wParam; xPos = LOWORD(lParam); yPos = HIWORD(lParam);

hwndは右クリックされたウィンドウのハンドルです。
xPosは右クリックされた場所のX座標(スクリーン座標)です。
yPosは右クリックされた場所のY座標(スクリーン座標)です。

クリックされた場所がわかってもどの項目が右クリックされたかは わかりませんね。こういうときはTreeView_HitTestを使います。

HTREEITEM TreeView_HitTest( hwnd, lpht );

hwndはツリービューのハンドルです。
lphtはTV_HITTESTINFO構造体へのポインタです。

戻り値はヒットされた項目のハンドルです

あー、また新しい構造体が出てきた!

typedef struct _TVHITTESTINFO { //tvhtst POINT pt; UINT flags; HTREEITEM hItem; } TV_HITTESTINFO, FAR *LPTV_HITTESTINFO;

ptはテストする点のクライアント座標を表すPOINT構造体です。

flagsはヒットテストの結果に関する情報を受け取る変数です。 たとえば、TreeView_HitTest(hwnd,&ht);を実行した後 if(ht.flags & TVHT_ONITEMBUTTON) XX();
とすれば「+」「−」のボタンをヒットしたとき関数XX()が 実行されます。(if(ht.flags == TVHT_ONITEMBUTTON)では ないことに注意!!)
TVHT_ONITEMはアイテムのラベルまたはビットマップ
TVHT_ONITEMBUTTONは「+」「-」ボタン
TVHT_ONITEMICONはアイテムのビットマップ
TVHT_ONITEMLABELはアイテムの文字列
他にもたくさんありますが、各自ヘルプで調べてみてください。

hItemは右クリックされた点に相当する項目のハンドルです。 (TreeView_HitTestの戻り値と同じ)

使い方としてはptをセットしてTreeView_HitTestを実行するだけです。

「志麻」という項目を右クリックすると左の図のように ポップアップメニューが出ます。これは、クラスメニューの 「編集(E)」メニューと同じです。

リソース・スクリプトは前章のものと全く同じなので省略します。

// treevw05.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #define ID_MYTREE 100 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void AddItem(HWND); char szClassName[] = "treevw05"; //ウィンドウクラス HINSTANCE hInst; char szAddItem[256]; //項目追加用 int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

ここは、いつも通りです。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); return (RegisterClassEx(&wc)); }

これも、いつもと変わりません。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; //インスタンスハンドルの保存 hWnd = CreateWindow(szClassName, "猫でもわかるツリービュー",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 154,//幅 197,//高さ NULL,//親ウィンドウのハンドル、親を作るときはNULL NULL,//メニューハンドル、クラスメニューを使うときはNULL hInst,//インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

前回と同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, ret; static HWND hTree; static HIMAGELIST hIList; TV_DISPINFO *ptv_disp; HTREEITEM hItem, hNewItem; TV_INSERTSTRUCT tvin; TV_HITTESTINFO tvhtst; switch (msg) { case WM_CREATE: InitCommonControls(); //コモンコントロールの初期化 hIList = ImageList_Create(16, 16, ILC_COLOR4 | ILC_MASK, 2, 0); ImageList_AddIcon(hIList, LoadIcon(hInst, "MYICONS")); ImageList_AddIcon(hIList, LoadIcon(hInst, "MYICONN")); if (hIList == NULL) MessageBox(hWnd, "イメージリスト作成失敗", "OK", MB_OK); hTree = CreateWindowEx(0, WC_TREEVIEW, "", WS_CHILD | WS_BORDER | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_EDITLABELS, 0, 0, 0, 0, hWnd, (HMENU)ID_MYTREE, hInst, NULL); TreeView_SetImageList(hTree, hIList, TVSIL_NORMAL); AddItem(hTree); break; case WM_CONTEXTMENU: tvhtst.pt.x = LOWORD(lp); tvhtst.pt.y = HIWORD(lp); ScreenToClient(hTree, &tvhtst.pt); hItem = TreeView_HitTest(hTree, &tvhtst); if (hItem) { HMENU hMenu, hSub; TreeView_SelectItem(hTree, hItem); hMenu = GetMenu(hWnd); hSub = GetSubMenu(hMenu, 1); TrackPopupMenu(hSub, TPM_CENTERALIGN, (int)LOWORD(lp), (int)HIWORD(lp), 0, hWnd, NULL); } break; case WM_NOTIFY: if (wp == ID_MYTREE) { ptv_disp = (TV_DISPINFO *)lp; if (ptv_disp->hdr.code == TVN_ENDLABELEDIT) { TreeView_SetItem(hTree, &ptv_disp->item); } } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_EDIT: hItem = TreeView_GetSelection(hTree); if (hItem) TreeView_EditLabel(hTree, hItem); else MessageBox(hWnd, "項目が選択されていません", "警告", MB_OK); break; case IDM_DEL: hItem = TreeView_GetSelection(hTree); if (hItem) TreeView_DeleteItem(hTree, hItem); else MessageBox(hWnd, "項目が選択されていません", "警告", MB_OK); break; case IDM_ADD: hItem = TreeView_GetSelection(hTree); if (hItem) { ret = DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); if (ret == IDOK) { tvin.hInsertAfter = TVI_LAST; tvin.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvin.hParent = hItem; tvin.item.pszText = szAddItem; tvin.item.iImage = 1; tvin.item.iSelectedImage = 0; hNewItem = TreeView_InsertItem(hTree, &tvin); TreeView_EnsureVisible(hTree, hNewItem); } } break; } break; case WM_SIZE: MoveWindow(hTree, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: ImageList_Destroy(hIList); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

WM_CONTEXTMENUのところを注意してください。 TV_HITTESTINFO構造体のptメンバをスクリーン座標のまま セットしてその後でScreenToClient関数でクライアント座標に 変換しています。これの反対にClientToScreen関数というのが あってすでに第44章で出ています。

BOOL ScreenToClient( HWND hWnd,// ウィンドウハンドル LPPOINT lpPoint // POINT構造体のアドレス );

これを実行するとPOINT構造体がクライアント座標に変換されます。

サブメニューのハンドルを取得してTrackPopupMenuを 実行しますが、GetSubMenu関数の第2引数は1であることに 注意してください。(「編集」は2番目のメニュー項目)

void AddItem(HWND hTree) { HTREEITEM hParent1, hParent2, hParent3, hChild1, hChild2; TV_INSERTSTRUCT tv; memset((char *)&tv, '\0', sizeof(tv)); tv.hInsertAfter = TVI_LAST; tv.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tv.hParent = TVI_ROOT; tv.item.pszText = "粂井"; tv.item.iImage = 1; tv.item.iSelectedImage = 0; hParent1 = TreeView_InsertItem(hTree, &tv); tv.item.pszText = "田中"; hParent2 = TreeView_InsertItem(hTree, &tv); tv.item.pszText = "佐藤"; hParent3 = TreeView_InsertItem(hTree, &tv); tv.hParent = hParent1; tv.item.pszText = "康孝"; hChild1 = TreeView_InsertItem(hTree, &tv); tv.item.pszText = "ひとみ"; hChild2 = TreeView_InsertItem(hTree, &tv); tv.hParent = hChild1; tv.item.pszText = "志麻"; TreeView_InsertItem(hTree, &tv); tv.hParent = hChild1; tv.item.pszText = "櫻都"; TreeView_InsertItem(hTree, &tv); tv.hParent = hParent2; tv.item.pszText = "マイケル"; TreeView_InsertItem(hTree, &tv); tv.hParent = hParent3; tv.item.pszText = "パトリシア"; TreeView_InsertItem(hTree, &tv); return; } LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(GetDlgItem(hDlg, IDC_EDIT1), szAddItem, sizeof(szAddItem)); EndDialog(hDlg, IDOK); break; case IDCANCEL: EndDialog(hDlg, IDCANCEL); break; } break; } return FALSE; }

上の2つの自作関数は前章と全く同じです。
[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Mar/15/1998 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。