第6回 ドラッグ&ドロップ

 

BTMemoを作る際、当初は独自の機構によるドラッグ&ドロップを考えていましたが、

他のプログラムとの連携を考えれば、標準(?)の方法を使用するほかありません。

それが、俗に言う「OLEドラッグ&ドロップ」です。

直接OLEを利用するためには、かなりの知識が必要かと思われますが、

今では、ほとんどの部分をカプセル化してくれているクラスライブラリが用意されているので

比較的簡単に機能を組み込むことができます。

 

ドラッグ&ドロップで使用するクラスは以下の3つです。

(1)COleDropSource

(2)COleDataSource

(3)COleDropTarget

 

(1)と(2)がドラッグを開始する側のプログラムで使用するクラスで、

(3)はドロップを受け入れる側のプログラムで使用するクラスです。

BTMemoはドラッグを開始できますし、ドラッグを受け入れることもできますので3つ全てを使用しています。

 

では順に見ていきます。

 

(1)COleDropSource

 

このクラスはドラッグ操作中の部分を担当しているようですが、BTMemoでは、

ドラッグ中のマウスカーソルを独自のものに変更するための手段として使用しています。

このクラスを直接使用するのではなく、派生クラス(CMyDropSource)を作成してGiveFeedbackというメンバ関数をオーバーライドします。

SCODE CMyDropSource::GiveFeedback( DROPEFFECT dropEffect )

{

        if (!g_Option.nUseTronPointer) {

                return DRAGDROP_S_USEDEFAULTCURSORS;

        }

        if (!m_bDragStarted) {

                return S_OK;

        }

        switch (dropEffect) {

        case DROPEFFECT_COPY:           // コピー操作中の場合。専用のマウスカーソルに置き換えます

                ::SetCursor(g_hGrip2);

                break;

 

        case DROPEFFECT_MOVE:           // 移動操作中の場合。専用のマウスカーソルに置き換えます

                ::SetCursor(g_hGrip);

                break;

 

        default:

                return DRAGDROP_S_USEDEFAULTCURSORS;

        }

        return S_OK;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

備考:

g_hGrip、g_hGrip2は、マウスカーソルのハンドルが格納されているグローバル変数で、プログラム起動時にハンドルが格納されています。

 

(これは本当に最近知ったばかりのものでして、この機能を組み込んだBTMemoはまだ未公開です)

 

 

 

(2)COleDataSource

 

このクラスは、(派生クラスを作らず)そのまま使用してます。

私は、CChildViewクラスのメンバ変数としています。

class CChildView : public CWnd

{

       

protected:

        COleDataSource  m_DataSource;

       

};

 

 

 

(3)ドラッグを開始する

 

ドラッグを開始する際の手順です。(プレーンテキストを送ると仮定します)

        //(A)ドラッグドロップで送るデータを作成します。

        //   クリップボードへ登録するデータとまったく同じに作成します。

        HGLOBAL hTXT = GetSelData();            // プレーンテキストのデータを作成する関数を仮定してます。

 

        //(B)ドラッグ&ドロップで送るデータを登録します。ここでCOleDataSourceクラスを使用します。

        m_DataSource.Empty();

        m_DataSource.CacheGlobalData(CF_TEXT, hTXT);    // 違うクリップボードフォーマットを指定して何個もデータを登録可能です。

 

        //(C)ドラッグ&ドロップを開始します。ドロップされるまで帰ってきませんのでご注意ください。

        //   COleDropSourceの派生クラスを指定することで、ドラッグ中のマウス制御が可能となります。

        CMyDropSource ds;

        DROPEFFECT dropEffect = m_DataSource.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, NULL, &ds);

 

        //(D)移動の場合の処理。何の事は無い、移動する場合は自分でデータを削除するのです。

        //   そういえば、クリップボードの「カット」も同じですね。

        if (dropEffect == DROPEFFECT_MOVE) {

                // ここでデータを削除します。

        }

 

        //(E)BTMemoでは。WM_LBUTTONDOWN内でドラッグを開始しています。

        //   このような場合WM_LBUTTONUPを偽装しないとおかしくなると聞きました。(本当かどうか未確認)

        PostMessage(WM_LBUTTONUP, nFlags, MAKELONG(point.x, point.y));

}

 

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

クラスのおかげで、クリップボードより簡単ですね。

 

 

 

(4)COleDropTarget

 

このクラスはドロップされる側の機能を担当しています。

派生クラス(CMyDropTarget)を作成してOnDragEnter、OnDragOver、OnDropなどというメンバ関数をオーバーライドします。

BTMemoではこの3つしかオーバーライドしていませんが、他にもいくつかオーバーライド可能なメンバ関数があるので

必要に応じて調べてみてください。

//(A)OnDragEnter

//   ドラッグ中のマウスカーソルが最初にウィンドウ上に移動してきたときに呼び出されます

DROPEFFECT CMyDropTarget::OnDragEnter(CWnd *pWnd, COleDataObject *pDataObject, DWORD dwKeyState, CPoint point)

{

        // BTMemoでは、単にマウス移動中の処理を呼び出しています

        return OnDragOver(pWnd, pDataObject, dwKeyState, point);

}

 

 

//(B)OnDragOver

//   ドラッグ中のマウスカーソルがウィンドウ上を移動してるときに呼び出されます

DROPEFFECT CMyDropTarget::OnDragOver(CWnd *pWnd, COleDataObject *pDataObject, DWORD dwKeyState, CPoint point)

{

        // ドロップ効果の初期化(なし)

        DROPEFFECT dropEffect = DROPEFFECT_NONE;

 

        // 引数の point を利用して、マウスカーソルがウィンドウの端に近い部分にある場合に

        // 自動スクロールの処理をしてますが、ここでは割愛します。

 

        // 処理可能なデータが含まれているかどうかを処理する優先度が高い順にチェックします。

        if (pDataObject->IsDataAvailable(CF_TAD)) {

                // 普通はコントロールキーの状態を見て、移動できるのかコピーできるのかを決めています。

                // ドロップするときの操作と機能のはドロップされる側が決めるのです。

                return  (pDi->dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;

        }

        else if (pDataObject->IsDataAvailable(CF_URL)) {

                // URLがドロップされる場合は、常にコピー操作にしてます。

                return DROPEFFECT_COPY;

        }

        else if (pDataObject->IsDataAvailable(CF_HDROP)) {

                return DROPEFFECT_COPY;

        }

        else if (pDataObject->IsDataAvailable(CF_BITMAP)) {

                return  (pDi->dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;

        }

        else if (pDataObject->IsDataAvailable(CF_TEXT)) {

                return  (pDi->dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;

        }

        // 上記以外はドロップされても受け取れません。

        return DROPEFFECT_NONE;

}

 

 

//(C)OnDrop

//   ドロップされたときに呼び出されます

BOOL CMyDropTarget::OnDrop(CWnd *pWnd, COleDataObject *pDataObject, DROPEFFECT dropEffect, CPoint point)

{

        // ドロップされたデータの中から優先度の高いデータを取り出します

        FORMATETC fmt = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};

        if (pDataObject->IsDataAvailable(CF_TAD)) {

                fmt.cfFormat = CF_TAD;

        }

        else if (pDataObject->IsDataAvailable(CF_URL)) {

                fmt.cfFormat = CF_URL;

        }

        else if (pDataObject->IsDataAvailable(CF_HDROP)) {

                fmt.cfFormat = CF_HDROP;

        }

        else if (pDataObject->IsDataAvailable(CF_BITMAP)) {

                fmt.cfFormat = CF_BITMAP;

        }

        else if (pDataObject->IsDataAvailable(CF_TEXT)) {

                fmt.cfFormat = CF_TEXT;

        }

        HGLOBAL hGlobal = pDataObject->GetGlobalData(fmt.cfFormat, &fmt);

 

        // これでクリップボードフォーマットとデータ(ハンドル)が手に入りました。

        // ここからはクリップボードの処理と一緒です。

        // また、データ(ハンドル)が自分のものでないのもクリップボードと一緒です。くれぐれも自分で開放しないように!

 

        return TRUE;

}

 

 

 

(5)ドロップを受け取る

 

派生クラスを作成したら、CChildViewクラスのメンバ変数とします。

あとは若干のおまじないをするだけで準備完了です。ドラッグされると勝手にメンバ関数がコールバックされます。

//(A)派生クラスをメンバ変数とします

class CChildView : public CWnd

{

       

protected:

        CMyDropTarget m_DropTarget;

       

};

 

 

//(B)「このウィンドウがドロップを受け付ける」という事を登録します

//   ウィンドウを登録するので、コンストラクタで実施しても無意味です(まだウィンドウができていない)

int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

       

        m_DropTarget.Register(this);

       

}

 

 

ソース見て思い出しながら書いたのですが… 

これで全部のはずだけど、何か忘れているような気がするんですよね。(なんだろ?気になるな…  (^^;; )