CEditView 雑記

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

1. 現在の選択文字列を取得する関数

現在選択されている文字列を取得するケースは結構あるのですが、CEditView のメンバ関数 GetSelectedText では、最初の改行文字までの文字列しか取得できません。MFC のソースを眺めてみるとこの関数は検索・置換関係でのみ使われており、検索・置換ダイアログでは改行入力が出来ないのでこういう仕様なのでしょうか?あまり汎用的な使用を前提として書かれているわけではなさそうです。そこで、独自に選択文字列を取得する関数を書いておきます。ちなみに、CMyView は CEditView からの派生クラスです。

// 選択テキストを取得
BOOL CMyView::GetSelText(CString& s) const
{
    int ss, se;
    GetEditCtrl().GetSel(ss, se);
    int nLen = se - ss;
    if (nLen == 0) return FALSE;
    LPCTSTR pText = LockBuffer();
    LPTSTR pBuf = s.GetBufferSetLength(nLen+1);
    lstrcpyn(pBuf, pText+ss, nLen+1);
    s.ReleaseBuffer();
    UnlockBuffer();
    return TRUE;
}

ついでに、開始位置と長さから文字列を取得する関数も書いておきましょう。

// 開始位置と長さから文字列を取得
BOOL CMyView::GetMidText(CString& s, int nStart, int nLen) const
{
    if (nLen <= 0) return FALSE;
    LPCTSTR pText = LockBuffer();
    LPTSTR pBuf = s.GetBufferSetLength(nLen+1);
    lstrcpyn(pBuf, pText+nStart, nLen+1);
    s.ReleaseBuffer();
    UnlockBuffer();
    return TRUE;
}

2. 32bit 対応の CharFromPos

CEdit の CharFromPos は、不思議な仕様になっています戻り値が下位ワードに文字インデックスを、上位ワードに行インデックスを返す、というものです。行インデックスは、文字インデックスが分かればいつでも、LineFromChar で取得出来るのに、文字インデックスの取得に 16bit 分しか用意されていないのでは、64KB 以上のテキストを扱うときに困ります。そこで、32bit が扱える CharFromPos を CMyView に付け加えておきます。

// 指定位置に最も近い文字のインデックスを得る
int CMyView::CharFromPos(CPoint pt) const
{
    CEdit& edit = GetEditCtrl();
    int start = edit.GetFirstVisibleLine();

    CDC* pDC = edit.GetDC();
    CFont* pOldFont = pDC->SelectObject(edit.GetFont());
    CRect r;
    edit.GetRect(&r);
    CSize size = pDC->GetTextExtent(_T('M'));
    int dy = (pt.y - r.top) / size.cy;
    int nChar = edit.LineIndex(start + dy);
    if (nChar == -1)
        nChar = GetWindowTextLength();
    else
    {
        int nLen = edit.LineLength(nChar);
        CPoint p1, p2, p3;
        int j, d1, d2;
        BOOL flag = FALSE;
        for (j=0; j<nLen; j++)
        {
            if (j==0)
            {
                p2 = PosFromChar(nChar+j);
                p3 = PosFromChar(nChar+j+1);
                d1 = p2.x - r.left;
                d2 = p3.x - (p3.x - p2.x) / 2;
            }
            else
            {
                p1 = PosFromChar(nChar+j-1);
                p2 = PosFromChar(nChar+j);
                p3 = PosFromChar(nChar+j+1);
                d1 = p2.x - (p2.x - p1.x) / 2;
                d2 = p3.x - (p3.x - p2.x) / 2;
            }
            if (pt.x >= d1 && pt.x < d2)
            {
                nChar += j;
                flag = TRUE;
                break;
            }
        }
        if (!flag) nChar += nLen;
    }
    pDC->SelectObject(pOldFont);
    edit.ReleaseDC(pDC);

#ifdef _MBCS
    LPCTSTR lpszText = LockBuffer();
    ASSERT(lpszText != NULL);
    if (_ismbstrail((const unsigned char *)lpszText, 
            (const unsigned char *)(lpszText+nChar)))
        nChar--;
    UnlockBuffer();
#endif

    return nChar;
}

実は、CEdit::PosFromChar にも問題があって(後述)、CMyView::PosFromChar で置き換えています。中ほどのややこしい(笑)コードは、指定された位置が文字の真中より右か左かでどちらの文字インデックスを返すかを判定するためのものです。

3. CEdit::PosFromChar の問題点

この関数 CEdit::PosFromChar には大きな問題点があります。引数の文字インデックスに最終カレット位置を指定すると、常に (-1,-1) を返すのです。つまり、最終文字の次の位置を取得出来ません。このことは今後、テキストのドラッグ&ドロップを実装するのに障害となることは明白です。

// 文字インデックスから位置を得る
CPoint CMyView::PosFromChar(int nChar) const
{
    CEdit& edit = GetEditCtrl();
    CPoint pt;
    pt = edit.PosFromChar(nChar);
    if (pt.x != -1 || pt.y != -1) return pt;

    TRACE(_T("%ld(%ld,%ld)-"), nChar, pt.x, pt.y);

    if (nChar <= 0)
    {
        CRect r;
        edit.GetRect(&r);
        pt.x = r.left;
        pt.y = r.top;
    }
    else
    {
        CDC* pDC = edit.GetDC();
        CFont* pOldFont = pDC->SelectObject(edit.GetFont());
        TEXTMETRIC tm;
        pDC->GetTextMetrics(&tm);

        int nLine = edit.LineFromChar(nChar-1);
        int nStart = edit.LineIndex(nLine);
        int nLen = edit.LineLength(nChar-1);
        if (nChar == nStart + nLen)
        {
            LPTSTR pBuf = new TCHAR [nLen+1];
            edit.GetLine(nLine, pBuf, nLen);
            pBuf[nLen] = _T('\0');
            int nTab = tm.tmAveCharWidth * m_nTabStops / 4;
            CSize size = pDC->GetTabbedTextExtent(
                            &(pBuf[nLen-1]),1,1,&nTab);
            delete [] pBuf;
            pt = edit.PosFromChar(nChar-1);
            pt.x += size.cx;
        }
        else
        {
            pt = edit.PosFromChar(nStart);
            pt.y += tm.tmHeight;
        }

        pDC->SelectObject(pOldFont);
        edit.ReleaseDC(pDC);
    }

    TRACE(_T("(%ld,%ld)\n"), pt.x, pt.y);

    return pt;
}

TRACE を入れてありますので、確認してみてください。

次ページ→

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