第215章 ドラッグリストボックス


ドラッグリストボックスとは、外見は通常のリストボックスと同じですが 項目をドラッグ・アンド・ドロップによってその順番を替えることの できるリストボックスのことです。



左の図はリストボックス中の「5番目の項目」をドラッグして 「1番目の項目」と「2番目の項目」の間に移動中のものです。 左端に見える小さいカーソルは自動的に出てきます。 図では表示されていませんがマウス位置に独自で作ったカーソルが 表示されます。



作り方は、比較的簡単です。

1.コモンコントロールの準備 2.普通にリストボックスを作る 3.MakeDragList関数を実行する

これだけでドラッグリストボックスを作ることができます。

BOOL MakeDragList( HWND hLB );

hLBはリストボックスのハンドルが来ます。
成功すると0以外が返されます。

しかし、これだけではドラッグリストボックスの機能を 果たしません。ドラッグリストボックスからの通知メッセージを 処理しなくてはなりません。

ドラッグリストボックスからの通知コードを得るには RegisterWindowMessage関数を呼ばなくてはいけません。

UINT RegisterWindowMessage( LPCTSTR lpString // メッセージ文字列のアドレス );

システムを通じてユニークな新しいメッセージを定義できます。 メッセージ文字列が同じなら他のアプリケーションでも同じ値が返されます。 この値は0xC000 から 0xFFFFまでのUINT値です。 関数が失敗したら0が返されます。

ドラッグリストボックスで使うにはlpStringにDRAGLISTMSGSTRINGを指定します。

ドラッグリストメッセージのwParam値はコントロールのIDとなります。
lParam値は、DRAGLISTINFO構造体のアドレスになります。

Typedef struct { UINT uNotification; HWND hWnd; POINT ptCursor; } DRAGLISTINFO, FAR *LPDRAGLISTINFO;

uNotificationには、ドラッグイベントに関する通知コードが入っています。

DL_BEGINDRAGユーザーがリスト項目の上で左クリックをした
DL_CANCELDRAGユーザーが右クリックしたかエスケープボタンを押して キャンセルした
DL_DRAGGINGユーザーが項目をドラッグ中である
DL_DROPPEDユーザーが左マウスボタンを離して、ドラッグ操作を 完了した

hWndには、ドラッグリストボックスのハンドルが入ります。

ptCursorは、マウスカーソルのX,Y座標を含んでいるPOINT構造体です。

さて、通知コードに対する処理はほぼワンパターンです。

DL_BEGINDRAG idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;

これが来たら、LBItemFromPt関数を呼んで移動するリスト項目を決定します。 TRUEを返すとドラッグ操作を始めます。FALSEを返すとドラッグ操作をやめます。

int LBItemFromPt( HWND hLB, POINT pt, BOOL bAutoScroll );

この関数はリストボックス中の特定の位置の項目インデックスを取得します。
hLBは、リストボックスのハンドルです。
ptは、スクリーン座標を含むPOINT構造体です。
bAutoScrollは、スクロールフラッグを示します。これがTRUEで、ポインターが リストボックスのまっすぐ上や下にあるとき、この関数はリストボックスを1行 スクロールして-1を返します。他の場合この関数はリストボックスをスクロールしません。

DL_CANCELDRAG idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;

これが来たら、移動する項目のIndexを-1にします。
戻り値はありません。

DL_DRAGGING idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;

これが来たら、LBItemFromPt関数で現在のマウス位置の項目IDを取得します。
そしてDrawInsert関数で「挿入アイコン」を表示します。これは、 上の図で左端のほうに見える小さい矢印型のアイコンです。不要な場合は 呼ばなくても問題ありません。

void DrawInsert( HWND handParent, HWND hLB, int nItem );

handParentは、ドラッグリストボックスの親ウィンドウです。
hLBは、ドラッグリストボックスのハンドルです。
nItemは、親ウィンドウ内に表示するリストボックスアイコン項目を 指定します。-1にするとアイコンが非表示になります。

さて、DL_DRAGGINGで次にすることは、LBItemFromPt関数でで得られた IDが-1以外のとき、独自のカーソルを設定することです。このときは 0を返します。
LBItemFromPt関数の戻り値が-1の時はDL_STOPCURSORカーソルを返します。

DL_DROPPED idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;

これが来たらやはりLBItemFromPt関数で現在のマウス位置の 項目アイテムのIDを取得します。
これが-1でないときは
移動項目のテキストを取得して、
それをリストから削除します。
そして、現在のリスト項目にその文字列を挿入します。
最後にこれを選択させます。

DrawInsert関数の最後の引数を-1にして「挿入アイコン」を非表示にします。

とまあこんなふうに処理をします。

では、プログラムを見てみましょう。

まず、左のようなカーソルを作ります。("MYDRAG")緑の部分は表示されません。 ホットスポットはカーソルの中心部に設定します。

// draglst1.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Cursor // MYDRAG CURSOR DISCARDABLE "mydrag.cur"

カーソルのリソース・スクリプトです。

// draglst1.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <commctrl.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "draglst1"; //ウィンドウクラス

コントロール関係のマクロを使うのでwindowsx.hを忘れずに インクルードしておいてください。
また、コモンコントロールの下準備も忘れずに行ってください。 (commctrl.hのインクルード、comctl32.libのリンク)

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; } //ウィンドウ・クラスの登録 ATOM 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 = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるドラッグリスト", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 230,//幅 140,//高さ 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; static HWND hList; static HINSTANCE hInst; LPCREATESTRUCT lpc; INITCOMMONCONTROLSEX ic; static UINT drgMsg; static LPDRAGLISTINFO lpd; UINT nMsg; static int nDragItem = -1, nListID; char szData[64]; if (msg == drgMsg) { lpd = (LPDRAGLISTINFO)lp; nMsg = lpd->uNotification; switch (nMsg) { case DL_BEGINDRAG: nDragItem = LBItemFromPt(lpd->hWnd, lpd->ptCursor, TRUE); return TRUE; case DL_DRAGGING: nListID = LBItemFromPt(lpd->hWnd, lpd->ptCursor, TRUE); DrawInsert(hWnd, lpd->hWnd, nListID); if (nListID != -1) { SetCursor(LoadCursor(hInst, "MYDRAG")); return 0; } return DL_STOPCURSOR; case DL_CANCELDRAG: nDragItem = -1; break; case DL_DROPPED: nListID = LBItemFromPt(lpd->hWnd, lpd->ptCursor, TRUE); if (nListID != -1) { ListBox_GetText(hList, nDragItem, szData); ListBox_DeleteString(hList, nDragItem); ListBox_InsertString(hList, nListID, szData); ListBox_SetCurSel(hList, nListID); } DrawInsert(hWnd, lpd->hWnd, -1); nDragItem = -1; break; } } switch (msg) { case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&ic); lpc = (LPCREATESTRUCT)lp; hInst = lpc->hInstance; hList = CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL, 10, 10, 200, 100, hWnd, (HMENU)100, hInst, NULL); MakeDragList(hList); drgMsg = RegisterWindowMessage(DRAGLISTMSGSTRING); ListBox_AddString(hList, "1番目の項目"); ListBox_AddString(hList, "2番目の項目"); ListBox_AddString(hList, "3番目の項目"); ListBox_AddString(hList, "4番目の項目"); ListBox_AddString(hList, "5番目の項目"); ListBox_AddString(hList, "6番目の項目"); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hList); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

いつもとはちょっと違う形のプロシージャです。まずは、見慣れたswitch (msg)の ところから見ていきます。

WM_CREATEメッセージが来たらコモンコントロールを初期化します。 ところでヘルプを見てもドラッグリストボックスのときにdwICCメンバを 何にすればよいのか書いてありません。仕方がないのでほとんどの コモンコントロールのときに使うICC_WIN95_CLASSESにしましたが、 これでよいのかどうかはわかりません。古いInitCommonControls関数は 今でも使えますがヘルプには、もう廃止されたと書かれています。 このへんは、もっとはっきりしてほしいものです。
次に通常のリストボックスを作ってMakeDragList関数で ドラッグリストにしています。
そのあと、RegisterWindowMessage関数でユニークなdrgMsgの値を 求めています。さて、この値をswitch(msg)の中でcase drgMsg:と するとコンパイラから「case 式は、整数型定数でなければなりません。」 と注意されます。それで、いつもとちょっと違う形になっています。

プログラム終了時にリストボックスを破棄していますが、親が破棄されるとき 子供も自動的に破棄されるのでDestroyWindow(hList);はなくても問題ありません。

次にプロシージャの最初の部分に戻ります。

swich-case構造に入る前にmsgがRegisterWindowMessage関数で求めた 値かどうかを調べて、そうならドラッグリストからの通知メッセージの 処理を行います。具体的処理はすでに解説してあるので照らし合わせてみてください。

さて、このプログラムではちょっと問題点があります。実際に作って 動かしてみるとわかるのですが、マウスの位置の 微妙な違いによって挿入される位置が違ってきます。いろいろ工夫してみてください。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

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