Programming Field - プログラミング Tips

RichEdit: 右クリックでテキストをドロップしたときにメニューを...

ここでは、「RichEdit: IRichEditOleCallback::GetContextMenu で GCM_RIGHTMOUSEROP を利用する方法」で書いた、IRichEditOleCallback::GetContextMenu を用いない方法を示します。こちらのほうが実用的かもしれません。

やりかたは、(結局は) IRichEditOleCallback をインプリメントし、(ここが違う) GetDragDropEffect を実装します。(他のメソッドはすべて E_NOTIMPL を返すだけで構いません。)

ちなみに GetDragDropEffect の定義は以下の通りです。

    STDMETHOD(GetDragDropEffect)(BOOL fDrag, DWORD grfKeyState, LPDWORD pdwEffect) PURE;

ここでキーになるのは引数 fDrag と grfKeyState です。(もちろん pdwEffect も重要です。)英語のヘルプを見てみると、fDrag については以下のように書かれています。(以下 MSDN より引用)

(fDrag) TRUE if the query is for a IDropTarget::DragEnter or IDropTarget::DragOver. FALSE if the query is for IDropTarget::Drop.

(適当訳: (fDrag が) TRUE のときは IDropTarget::DragEnter か IDropTarget::DragOver で使用できる(pdwEffect の)値、FALSE のときは IDropTarget::Drop で使う値を(pdwEffect に)返します。)

最初自分は IDropTarget のそれぞれのメソッドの実装から呼び出されるのかと思ってましたが、そうではなく、実際は (fDrag == TRUE) は1度だけ、(fDrag == FALSE) は複数回にわたって呼び出されました。これは、fDrag が TRUE のときにドラッグの効果(移動、コピー、リンク、無効など)として使用できる値すべてを取得し、その後コントロールがドラッグ中に fDrag を FALSE としてメソッドを呼び出し、返された値が最初に取得した値に含まれなければ、その効果を無効とするようです。

そこで、ドロップされたこと(ドラッグ完了)を知る手段は、grfKeyState が 0 かどうかです。実は、(IDropTarget::Drop でも)ドロップ時は grfKeyState が 0 になっています(Windows 95、XP で確認済み)。なので、上記も踏まえて、コードは例えば以下のようになります。

// m_dwKeyStateDrop は DWORD 型のクラス内変数

STDMETHODIMP CROCallback::GetDragDropEffect(BOOL fDrag,
    DWORD grfKeyState, LPDWORD pdwEffect)
{
    // fDrag が TRUE のときは使用できるドラッグの効果を返す
    if (fDrag)
    {
        *pdwEffect = DROPEFFECT_COPY | DROPEFFECT_MOVE;
        return S_OK;
    }
    // 以下 fDrag が FALSE のとき

    // grfKeyState が 0 でないときは値をキープしておく
    // (ドロップ時の動作に影響するため)
    if (grfKeyState)
        m_dwKeyStateDrop = grfKeyState;
    // 右クリックによるドラッグのとき、または Ctrl が
    // 押されているときは、「コピー」にする
    if (m_dwKeyStateDrop & (MK_RBUTTON | MK_CONTROL))
        *pdwEffect = DROPEFFECT_COPY;
    // 他はすべて「移動」
    else //if (m_dwKeyStateDrop & (MK_SHIFT))
        *pdwEffect = DROPEFFECT_MOVE;
    // grfKeyState が 0 でないとき(ドラッグ中)は処理終了
    if (grfKeyState)
        return S_OK;
    // 右クリックでドロップされたときはポップアップメニュー
    // (コンテキストメニュー)を表示する
    if (m_dwKeyStateDrop & MK_RBUTTON)
    {
        UINT uID;
        POINT pt;
        ::GetCursorPos(&pt);
        // メソッド内で処理するのが理想なので
        // TPM_RETURNCMD を利用する
        uID = ::TrackPopupMenuEx(g_hDropMenu,
            TPM_RETURNCMD | TPM_NONOTIFY,
            pt.x, pt.y, g_hWndMain, NULL);
        // メニューの表示が出来なかった、または
        // ドロップキャンセルが選択されたとき
        if (!uID || uID == ID_DROP_CANCEL)
        {
            *pdwEffect = DROPEFFECT_NONE;
            return S_OK;
        }
        // 「ここにコピー」の場合
        if (uID == ID_DROP_COPYHERE)
            *pdwEffect = DROPEFFECT_COPY;
        // 「ここに移動」の場合
        else if (uID == ID_DROP_MOVEHERE)
            *pdwEffect = DROPEFFECT_MOVE;
        // ※ 上記の ID (uID) は架空のものです。
    }
    return S_OK;