CEditView 雑記

Last Updated: Apr.4,2003
使用しているコンパイラは、Microsoft Visual C++ 6.0 です。これ以外のバージョンでは記述と食い違うことがあるかも知れません。

5. テキストのドラッグ&ドロップの実装(OLE)

Windows 付属のワードパットのように、テキストをドラッグ&ドロップで編集できると非常に使いやすいエディタになります。これは、OLE という機能を利用して実装するのですが、OLE についての具体的な説明は私も詳しくは知らないので、ここでは割愛させて頂きます。手順としては、

  1. CEditView の派生クラスに COleDropTarget オブジェクトのメンバを持たせて、OLE にドロップターゲットであると通知する。
  2. OnLButtonDown で COleDataSource オブジェクトを作成し、COleDataSource::DoDragDrop を呼び出し、その戻り値を判別して、テキストのコピーや移動の実際の処理を行う。
  3. 仮想関数 OnDragEnter で、ドラッグ中にクライアント領域に最初に入ったときのカーソル位置の更新・表示。
  4. 仮想関数 OnDragOver で、ドラッグ中にクライアント領域内で移動しているときのカーソル位置の更新・表示。
  5. 仮想関数 OnDrop で、ドロップ位置を決定し、ドロップした結果が成功か失敗かを返す。

といった感じです。

OLE の初期化とドロップターゲットへの登録

まず、OLE を利用するためには、アプリケーションでの初期化が必要です。CWinApp::InitInstance で一度だけ行います。

BOOL CMyApp::InitInstance()
{
    if (!AfxOleInit()) /* OLE の初期化 */
        return FALSE;

    ・・・

次に、CMyView に COleDropTarget 型のメンバ m_dropTarget を持たせ、OnCreate で COleDropTarget::Register を呼び出します。

int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CEditView::OnCreate(lpCreateStruct) == -1)
        return -1;

    /* ドロップターゲットへの登録 */
    if (!m_dropTarget.Register(this))
        return -1;

    return 0;
}

OnLButtonDown での処理

OnLButtonDown が処理の入り口(であり出口)です。ドロップ位置を示す文字インデックスが分からないとドロップ後の処理ができないので、m_nDropChar メンバを追加しています。後で OnDrop でこの変数に値を設定することになります。

void CMyView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    if (PtInSel(point))
    {   /* point が選択文字列内にあるとき */
        int ss, se;
        CEdit& edit = GetEditCtrl();
        edit.GetSel(ss, se);
        CString sSel;
        GetSelText(sSel); /* 選択文字列取得 */

        /* OLE に文字列を保持 */
        HGLOBAL hData = GlobalAlloc(GHND | GMEM_SHARE,
                                    (sSel.GetLength()+1)*sizeof(TCHAR));
        lstrcpy((LPTSTR)GlobalLock(hData), (LPCTSTR)sSel);
        GlobalUnlock(hData);
        COleDataSource dataSource;
        dataSource.CacheGlobalData(CF_TEXT, hData);

        /* ドロップ&ドロップ操作を実行 */
        DROPEFFECT result = dataSource.DoDragDrop(DROPEFFECT_COPY|DROPEFFECT_MOVE);

        if (result == DROPEFFECT_MOVE)
        {   /* 選択文字列を消す */
            edit.Clear();
        }

        if (result == DROPEFFECT_COPY || result == DROPEFFECT_MOVE)
        {
            if (m_nDropChar < ss)
            {   /* ドロップ位置がドラッグより前 */
                ss = m_nDropChar;
                edit.SetSel(ss, ss, TRUE);
                edit.ReplaceSel(sSel);
                edit.SetSel(ss, ss+sSel.GetLength());
            }
            else if (m_nDropChar > se)
            {   /* ドロップ位置がドラッグより後 */
                if (result == DROPEFFECT_COPY)
                    ss = m_nDropChar;
                else if (result == DROPEFFECT_MOVE)
                    ss = m_nDropChar - sSel.GetLength();
                edit.SetSel(ss, ss, TRUE);
                edit.ReplaceSel(sSel);
                edit.SetSel(ss, ss+sSel.GetLength());
            }
        }
        return;
    }

    CEditView::OnLButtonDown(nFlags, point);
}

OnDragEnter と OnDragOver

この2つはまったく同じ処理でもいけそうです。OnDragEnter のみ示します。

DROPEFFECT CMyView::OnDragEnter(COleDataObject* pDataObject, 
                                DWORD dwKeyState, CPoint point) 
{
    if (pDataObject->IsDataAvailable(CF_TEXT))
    {
        CRect r;
        GetEditCtrl().GetClientRect(&r);
        if (!r.PtInRect(point))
            return DROPEFFECT_NONE;

        /* カレット位置を更新・表示 */
        int nChar = CharFromPos(point);
        CPoint pt = PosFromChar(nChar);
        if (!PtInSel(pt)) SetCaretPos(pt);

        return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
    }

    return CEditView::OnDragEnter(pDataObject, dwKeyState, point);
}

主な仕事はカレット位置を更新して、表示することです。この処理がないと「福笑い」になってしまいます。

OnDrop での処理

OnDrop では、m_nDropChar にドロップ位置を設定して、結果を返すだけです。

BOOL CMyView::OnDrop(COleDataObject* pDataObject,
                     DROPEFFECT dropEffect, CPoint point) 
{
    if (pDataObject->IsDataAvailable(CF_TEXT))
    {
        /* カレット位置を更新・表示 */
        m_nDropChar = CharFromPos(point);
        CPoint pt = PosFromChar(m_nDropChar);
        if (PtInSel(pt, TRUE))
            return FALSE;
        SetCaretPos(pt);

        return TRUE;
    }

    return CEditView::OnDrop(pDataObject, dropEffect, point);
}

案外簡単ですね。

その他の処理

骨組みはこれで大体 OK なのですが、残念ながら実用にならない点があります。ここでは示しませんが、

  1. マウスカーソルが選択範囲上に来たときにカーソルを矢印にする(OnSetCursor)
  2. ドラッグしながら、ウィンドウをスクロールする処理(OnDragScroll)
  3. 他のアプリケーションやウィンドウからドロップされた場合の処理(OnDrop)

などの処理が必要です。項目2では、OnDragScroll 内で CEdit::LineScroll を使うとうまくできますので試してみてください。

←前ページ 次ページ→

Back Copyright (c) 2003 AOK. All Rights Reserved.