ドラッグ アンド ドロップ(CListCtrl編)

CListCtrlの場合のドラッグ アンド ドロップ(以下D&D)です。
他のコントロールでも利用できると思います。

D&Dを実装するにはいくつかのメッセージを処理する必要があります。
そのメッセージは
・LVN_BEGINDRAG
・WM_LBUTTONUP
・WM_MOUSEMOVE
です。
LVN_BEGINDRAGはD&Dの準備を行いなさいというメッセージです。
WM_LBUTTONUPはマウスの左ボタンが離されたというメッセージです。
WM_MOUSEMOVEはマウスが移動しているというメッセージです。
これらはClassWizardで追加できるのでClassWizardを利用して追加します。

LVN_BEGINDRAGのメッセージの処理

ClassWizardで追加すると、LVN_BEGINDRAGに対するマップが作成され、関数が作成されます。
(特に変更を行わないとOnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)が追加される)

LVN_BEGINDRAGにマップされる関数
void CHoge::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; *pResult = 0; }
名前 説明
pNMHDR LVN_BEGINDRAGメッセージを出したコントロールのハンドル
pResult 戻り値
ClassWizardで追加すると、pNMHDRはListViewとしてキャストされ、pNMListViewにキャストの結果が入っていると思います。
pNMListViewにはいくつかのメンバ変数が定義されており、そこから必要な情報を取得することが可能です。

あと、ここではD&Dで必要なメンバ変数の値を設定します。
D&Dで必要ではないかと思われる変数は以下の物です。
(ただし、目的によって変わると思うので注意)
・D&Dをしているかどうか
・D&D用のイメージを格納するイメージリスト
・D&Dするアイテムやアイテムの位置
等だと思います。
これらをここで設定します。

OnBeginDrag()関数での処理
private:
    CPoint m_dragPos;           //D&Dをしている位置
    CImageList m_lpDragImage;   //D&D用のイメージを格納するImageList
    BOOL m_isDragDrop;          //TRUE:D&Dしている / FALSE:D&Dしていない

・・・中略・・・

void CHoge::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) {
    NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

    //D&Dの準備を行う
    POINT *drPt = &pNMListView->ptAction;

    m_dragPos = CPoint(*drPt);
    if(pNMListView->iItem != -1){

        //ドラッグ用のイメージを取得する
        m_lpDragImage = CreateDragImage(pNMListView->iItem, drPt);
        if(m_lpDragImage != NULL){

            //D&Dの開始
            m_lpDragImage->BeginDrag(0, CPoint(0,0));
            m_lpDragImage->DragEnter(this, CPoint(*drPt));
            SetCapture();

            //D&D管理変数をTRUEに
            isDragDrop = TRUE;
        }else{
            isDragDrop = FALSE;
        }
    }
    *pResult = 0;
}
この中で使われている関数について。
CreateDragImage()関数はCListCtrlの関数で、D&D用のイメージリストを作成します。
BeginDrag()関数はCImageListの関数で、D&Dの開始を意味します。
DragEnter()関数はCImageListの関数で、ドラッグ中の更新をロックし、 指定した位置にドラッグ イメージを表示します。

これらの関数についてはMSDNをみてください。

あと、D&D用のイメージは自分で作成することも可能です。
もCListCtrlのCreateDragImage()関数を利用する必要はありません。

WM_MOUSEMOVEのメッセージの処理

ClassWizardで追加すると、WM_MOUSEMOVEに対するマップが作成され、関数が作成されます。
(特に変更を行わないとOnMouseMove(UINT nFlags, CPoint point)が追加される)

LVN_BEGINDRAGにマップされる関数
void CHoge::OnMouseMove(UINT nFlags, CPoint point) { CListCtrl::OnMouseMove(nFlags, point); }
名前 説明
nFlags 押されている仮想キー
point 現在のマウスの位置
なぜ、WM_MOUSEMOVEのメッセージの処理を行わなければならないか。
答えは至って単純で、このメッセージで受け取るpointの座標から、 D&D画像の表示位置を変更するからです。
イメージを表示させない場合はいらない処理なんですが(^^;

ここで気をつけないといけないのが、pointの値です。
pointは親ウィンドウ上の座標らしく、CListCtrlの座標に変換する必要があることです。

OnMouseMove()関数での処理
void CHoge::OnMouseMove(UINT nFlags, CPoint point) {
    if(m_isDragDrop){
        //ドラッグ アンド ドロップ
        m_dragPos = point;

        MapWindowPoints(this, &m_dragPos, 1);

        //イメージを現在のマウスの位置に移動
        m_lpDragImage->DragMove(m_dragPos);
    }
    CListCtrl::OnMouseMove(nFlags, point);
}
ここで目に付くのは
MapWindowPoints()関数DragMove()関数です。
MapWindowPoints()関数は、CWnd の座標空間からほかのウィンドウの座標空間へ変換します。
この場合は、CLIstCtrlの座標です。

DragMove()関数はCImageListの関数で、指定した位置にD&Dのイメージを移動させます。

WM_LBUTTONUPのメッセージの処理

ClassWizardで追加すると、WM_LBUTTONUPに対するマップが作成され、関数が作成されます。
(特に変更を行わないとOnLButtonUp(UINT nFlags, CPoint point)が追加される)
ここでは、D&Dの終了処理を行います。

WM_LBUTTONUPにマップされる関数
void CHoge::OnLButtonUp(UINT nFlags, CPoint point) { CListCtrl::OnLButtonUp(nFlags, point); }
名前 説明
nFlags 押されている仮想キー
point 現在のマウスの位置
引数はWM_MOUSEMOVEと同じです。

OnLButtonUp()関数での処理
void CHoge::OnLButtonUp(UINT nFlags, CPoint point) {
    CListCtrl::OnLButtonUp(nFlags, point);
    if(m_isDragDrop){
        ReleaseCapture();
        m_lpDragImage->DragLeave(this);
        m_lpDragImage->EndDrag();

        //アイテムの移動処理を記述

        delete m_lpDragImage;
        m_lpDragImage = NULL;
        m_isDragDrop = FALSE;
    }
}
ここで行っておかなければならない処理は、
・D&Dの終了処理
・D&D用のイメージリストの解放

です。

DragLeave()関数はCLimageListの関数で、更新のロックを解除します。
これにより、ドラッグ後の状態の描画が可能になります。
EndDrag()関数はCLimageListの関数で、で、イメージのドラッグを終了します。

D&D用のイメージリストの解放は必須です。
とはフラグ関係をFALSEにしてD&Dをしていない状態を作ります。

イメージを利用しない場合

イメージを利用しない場合は、イメージリストに関する処理をすべて省略すればよいだけです。