ViViビルド#0125でやっとドラッグ&ドロップ(以下DnDと略す)をOLEにより実装しました。
それ以前は、マウス関係のハンドラで自前で処理していましたが、ViVi内の異なるウィンドウ間や、
別のアプリケーションとの間でDnDできないので、
OLEによる実装に変更して欲しいという要望に応えたものです。
MFC 4.x ではOLEのためのクラスが定義されており、COMインタフェース等を完全に包み隠しているので、
OLEを完全に理解していなくても、簡単にプログラムが記述できます。
しかし実際に実装してみると、やっぱり(本質と関係無いところで)結構苦労しました。
以下はその苦労の記録でもあります。これからOLEのプログラムを組んでみようという方の参考になれば幸いです。
ドロップソース側: ドロップターゲット側: ┏━━━━━━━━━┓ ┏━━━━━━━━━┓ ┃CView ┃ ┃CView ┃←─┐ ┃┌───────┐┃ ┃ ┃ │ハンドラメソッドを ┃│OnLButtonDown │┃ ┃┌───────┐┃ │ コール ┃│ ハンドラ │┃ ┃│COleDropTarget├╂──┘ ┃└───────┘┃ ┃└───────┘┃ ┃ │ ┃ ┃ ↑ ┃ ┗━━━━┿━━━━┛ ┗━━━━┿━━━━┛ │・インスタンス化 │ │・データを設定 │ ↓・DoDragDrop をコール │ ┌───────┐ ┌───────┐ │COleDataSource│───────────→│COleDataObject│ └───────┘ COMによる通信 └───────┘ |
このようにOLEによるDnD実装はオブジェクト指向化されており、 ソースとターゲットで独立にコードを記述するのが特徴的です。 他の部分との依存関係をあまり考えなくてもよいので、コードの記述はかなり楽ですが、 ソースとターゲットのビューが同じだったり、同じスプリットウィンドウの異なるペインだったり、 同じドキュメントの異なるビューの場合は、マルチスレッドの場合の同期処理のようなものが必要になります。
今回のOLEの実装で一番時間がかかった(数時間を要した)のは、このOLEの初期化を行う部分でした。
OLEの初期化は CWinApp 派生クラスの InitInstance で、
BOOL CViviApp::InitInstance() { if( !AfxOleInit() ) // OLEの初期化 return FALSE; ..... }とします。このような簡単なコードを記述するのに、どうして数時間も要してしまったのか、 不思議に思われるかもしれませんが、最初は『OLEの初期化が必要だということが解らなかった』からです。!!!
教訓:人は自分が知っていることしか知らない。
ドロップソース側の実装は簡単です。CViviView::OnLButtonDown で選択されている領域がクリックされた場合に、
COleDataSource のインスタンスを作成し、それにDnDするデータ(選択されているテキスト)を結合し、
COleDataSource::DoDragDrop をコールします。
このメソッドはDnD処理を行い、その結果、コピーかムーブかキャンセルか(またはリンク)を返します。
ムーブの場合はソース側で選択領域を削除します。
たったこれだけのコードで、後はMFC/COMが処理を引き受けてくれるので、
登録されているドロップターゲット(Word 等)に対しDnD操作を行うことが可能になります。らくちんですね。
void CViviView::OnLButtonDown(UINT nFlags, CPoint point) { if( isSelectedMode() && isSelectedArea(point.x, point.y) ) { // 選択領域をクリックした場合 HGLOBAL hGlobal = GlobalAlloc(GHND,...); // DnDするデータ領域を作成 hGlobal にデータを設定; COleDataSource dataSource; // データソースオブジェクトをインスタンス化 dataSource.CacheGlobalData(CF_TEXT, hGlobal); // 作成したデータを設定 DROPEFFECT result = dataSource.DoDragDrop(DROPEFFECT_COPY|DROPEFFECT_MOVE); if( result == DROPEFFECT_MOVE ) { 選択されていた領域を削除する; } return; } ..... }
プログラムは上記のようになります。ここでは、プレーンテキスト(CF_TEXT)をデータとして設定しています。
ドロップターゲット側の実装もそれほど手間ではありません。作業は以下の3つだけです。
以下、簡単に説明します。
以下の様にメンバ変数を追加します。
class CViviView : public CView { protected: ..... COleDropTarget m_dropTarget; // OLE DnD のドロップターゲット ..... };
クラスウィザードで OnCreate をオーバライドし、 以下のようにビューウィンドウをドロップターゲットとして(COMに?)登録します。 これで、ビューがDnDターゲットになった場合は、3のメソッドたちがコールされるようになります。
int CViviView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; if( m_dropTarget.Register( this ) ) // ドロップターゲットへの登録 return 0; else return -1; }
OnDragEnter はDnD中にマウスカーソルがターゲットウィンドウに入ったときにコールされます。
OnDragLeave は逆にウィンドウから出ていったときです。
OnDragOver はウィンドウ内でのマウスカーソルが移動したときにコールされます。
座標を調べ、ドロップ可能かどうかをCOMを通じてドロップソース側に通知します。
OnDrop は実際にドロップ処理を行うメソッドです。
DnDされるデータ形式はプレーンテキストだけではなく、ビットマップや、
ユーザが独自に定義した形式も可能です。したがって、
各メソッドではDnDされているデータの形式を調べ、それに従った処理を行います。
それぞれの実装例を以下に示します。
DROPEFFECT CViviView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { if( pDataObject->IsDataAvailable(CF_TEXT) ) { int line, offset, column; pointToPosition(point, line, offset, column); // マウス位置からカーソルを設定 カーソルを表示; return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; // コントロールの状態により、コピーがどうかを通知 } ..... // 他のデータ形式の処理... } void CViviView::OnDragLeave() { // CF_TEXT では特にすることは無い } DROPEFFECT CViviView::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { if( pDataObject->IsDataAvailable(CF_TEXT) ) { int line, offset, column; pointToPosition(point, line, offset, column); if( line != m_caretLine || column != m_caretColumn ) { カーソル位置を更新・表示; } return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; } ..... // 他のデータ形式の処理... } BOOL CViviView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) { if( pDataObject->IsDataAvailable(CF_TEXT) ) { HGLOBAL hData = pDataObject->GetGlobalData(CF_TEXT); ASSERT(hData); const char *ptr = (const char *)GlobalLock(hData); // ドロップするデータを取得 doInsertText(ptr); // データをドキュメントに挿入 GlobalUnlock(hData); return TRUE; } ..... // 他のデータ形式の処理... }
OLEでDnDを行った場合、同一インスタンス内であれば、
DnD後にターゲットのビューをアクティブにしたいものです。この実装は実は簡単なのですが、
OLEの初期化の次に時間がかかって(やはり数時間)しまいました。
ビューウィンドウをアクティベイトしてもダメで、CMDIChildWnd
とスプリットウィンドウのペインをアクティベイトする必要があります。
コードは以下のような感じです。
BOOL CViviView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) { if( pDataObject->IsDataAvailable(CF_TEXT) ) { ..... CSplitterWnd *spltWnd = (CSplitterWnd *)GetParent(); spltWnd->SetActivePane(0, 0, this); ((CMDIChildWnd *)spltWnd->GetParent())->MDIActivate(); // ターゲットのビューをアクティブに return TRUE; } ..... // 他のデータ形式の処理... }
前のTips 津田伸秀 のホームページに戻る。
Last Modified on 26-Jan-1997 21:42:27,
Copyright (c) 1996 by Nobuhide Tsuda, All Right Reserved.
このホームページに関するご質問、ご要望、バグレポート等は
ntsuda@beam.or.jp
までメールをいただければ幸いです。