左の図はリストボックス中の「5番目の項目」をドラッグして
「1番目の項目」と「2番目の項目」の間に移動中のものです。
左端に見える小さいカーソルは自動的に出てきます。
図では表示されていませんがマウス位置に独自で作ったカーソルが
表示されます。
作り方は、比較的簡単です。
これだけでドラッグリストボックスを作ることができます。1.コモンコントロールの準備 2.普通にリストボックスを作る 3.MakeDragList関数を実行する
hLBはリストボックスのハンドルが来ます。BOOL MakeDragList( HWND hLB );
しかし、これだけではドラッグリストボックスの機能を 果たしません。ドラッグリストボックスからの通知メッセージを 処理しなくてはなりません。
ドラッグリストボックスからの通知コードを得るには RegisterWindowMessage関数を呼ばなくてはいけません。
システムを通じてユニークな新しいメッセージを定義できます。 メッセージ文字列が同じなら他のアプリケーションでも同じ値が返されます。 この値は0xC000 から 0xFFFFまでのUINT値です。 関数が失敗したら0が返されます。UINT RegisterWindowMessage( LPCTSTR lpString // メッセージ文字列のアドレス );
ドラッグリストボックスで使うにはlpStringにDRAGLISTMSGSTRINGを指定します。
ドラッグリストメッセージのwParam値はコントロールのIDとなります。
lParam値は、DRAGLISTINFO構造体のアドレスになります。
uNotificationには、ドラッグイベントに関する通知コードが入っています。Typedef struct { UINT uNotification; HWND hWnd; POINT ptCursor; } DRAGLISTINFO, FAR *LPDRAGLISTINFO;
DL_BEGINDRAG | ユーザーがリスト項目の上で左クリックをした |
DL_CANCELDRAG | ユーザーが右クリックしたかエスケープボタンを押して キャンセルした |
DL_DRAGGING | ユーザーが項目をドラッグ中である |
DL_DROPPED | ユーザーが左マウスボタンを離して、ドラッグ操作を 完了した |
hWndには、ドラッグリストボックスのハンドルが入ります。
ptCursorは、マウスカーソルのX,Y座標を含んでいるPOINT構造体です。
さて、通知コードに対する処理はほぼワンパターンです。
これが来たら、LBItemFromPt関数を呼んで移動するリスト項目を決定します。 TRUEを返すとドラッグ操作を始めます。FALSEを返すとドラッグ操作をやめます。DL_BEGINDRAG idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;
この関数はリストボックス中の特定の位置の項目インデックスを取得します。int LBItemFromPt( HWND hLB, POINT pt, BOOL bAutoScroll );
これが来たら、移動する項目のIndexを-1にします。DL_CANCELDRAG idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;
これが来たら、LBItemFromPt関数で現在のマウス位置の項目IDを取得します。DL_DRAGGING idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;
handParentは、ドラッグリストボックスの親ウィンドウです。void DrawInsert( HWND handParent, HWND hLB, int nItem );
さて、DL_DRAGGINGで次にすることは、LBItemFromPt関数でで得られた
IDが-1以外のとき、独自のカーソルを設定することです。このときは
0を返します。
LBItemFromPt関数の戻り値が-1の時はDL_STOPCURSORカーソルを返します。
これが来たらやはりLBItemFromPt関数で現在のマウス位置の 項目アイテムのIDを取得します。DL_DROPPED idCtl = (WPARAM)(int) wParam; pDragInfo = (LPARAM)(LPDRAGLISTINFO) lParam;
DrawInsert関数の最後の引数を-1にして「挿入アイコン」を非表示にします。
とまあこんなふうに処理をします。
では、プログラムを見てみましょう。
まず、左のようなカーソルを作ります。("MYDRAG")緑の部分は表示されません。
ホットスポットはカーソルの中心部に設定します。
カーソルのリソース・スクリプトです。// draglst1.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Cursor // MYDRAG CURSOR DISCARDABLE "mydrag.cur"
コントロール関係のマクロを使うのでwindowsx.hを忘れずに インクルードしておいてください。// 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"; //ウィンドウクラス
いつもと同じですが、親ウィンドウ(メイン・ウィンドウ)のサイズは ドラッグリストボックスのサイズに合わせています。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; }
いつもとはちょっと違う形のプロシージャです。まずは、見慣れたswitch (msg)の ところから見ていきます。//ウィンドウプロシージャ 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; }
WM_CREATEメッセージが来たらコモンコントロールを初期化します。
ところでヘルプを見てもドラッグリストボックスのときにdwICCメンバを
何にすればよいのか書いてありません。仕方がないのでほとんどの
コモンコントロールのときに使うICC_WIN95_CLASSESにしましたが、
これでよいのかどうかはわかりません。古いInitCommonControls関数は
今でも使えますがヘルプには、もう廃止されたと書かれています。
このへんは、もっとはっきりしてほしいものです。
次に通常のリストボックスを作ってMakeDragList関数で
ドラッグリストにしています。
そのあと、RegisterWindowMessage関数でユニークなdrgMsgの値を
求めています。さて、この値をswitch(msg)の中でcase drgMsg:と
するとコンパイラから「case 式は、整数型定数でなければなりません。」
と注意されます。それで、いつもとちょっと違う形になっています。
プログラム終了時にリストボックスを破棄していますが、親が破棄されるとき 子供も自動的に破棄されるのでDestroyWindow(hList);はなくても問題ありません。
次にプロシージャの最初の部分に戻ります。
swich-case構造に入る前にmsgがRegisterWindowMessage関数で求めた 値かどうかを調べて、そうならドラッグリストからの通知メッセージの 処理を行います。具体的処理はすでに解説してあるので照らし合わせてみてください。
さて、このプログラムではちょっと問題点があります。実際に作って 動かしてみるとわかるのですが、マウスの位置の 微妙な違いによって挿入される位置が違ってきます。いろいろ工夫してみてください。
Update 17/Jul/1999 By Y.Kumei