第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); … } |
ソース見て思い出しながら書いたのですが…
これで全部のはずだけど、何か忘れているような気がするんですよね。(なんだろ?気になるな… (^^;; )