キャレットの位置に関係なく、RichTextBoxコントロールを特定のポイントまでスクロールする方法

Dec 10 2020

WinFormsアプリには、長いテキストを含むRichTextBoxコントロールがあります。選択キャレットがどこにあるかに関係なく、プログラムで特定のポイント(文字インデックスとして表される)までスクロールする必要があります。私はこのような方法が必要です:

//scroll the control so that the 3512th character is visible.
rtf.ScrollToPosition(3512);

私が見つけた同様の質問に対するすべての回答ScrollToCaret()は、この方法を使用しています。これは、キャレットの位置までスクロールする場合に問題ありません。しかし、キャレットの位置を変更せずに、キャレットではなく別の位置にスクロールする必要があります。どうすればよいですか?

ありがとうございました。

回答

1 Jimi Dec 11 2020 at 04:09

SendMessageを使用して、WM_VSCROLLメッセージをRichEditコントロールに送信し、SB_THUMBPOSITIONのでLOWORDwParamおよびでスクロールする絶対垂直位置を指定できますHIWORD

GetPositionFromCharIndexの方法は、(に属するTextBoxBaseそれはあまりにも、テキストボックスのクラスに適用されるように)相対返し物理特定の位置におけるチャーがチャーの位置は、現在のスクロール位置の上にあり、それの場合(値が負であることができる表示されている位置を現在のスクロール位置とその下にある場合のchar位置の差-現在のスクロール位置が0)でない限り。


RichTextBoxの名前がrichTextBox1次のとおりであると想定します。

  • たとえば、Regex.Matchを使用して、単語またはフレーズの位置を決定します。一致したパターンの位置は、MatchのIndexプロパティによって返されます。
  • 現在のオフセットを確認してください GetPositionFromCharIndex(0)
  • 現在の垂直位置によって定義されたオフセットの絶対値を、によって返される値(ピクセルで表される)に追加しますGetPositionFromCharIndex(matchPos)。ここで、matchPosはスクロール先の文字/単語/パターンの位置です。
  • SendMessage計算された位置を使用して呼び出しSB_THUMBPOSITION、wParamの一部として渡されるこの位置に親指を移動するように指定します。
var matchPos = Regex.Match(richTextBox1.Text, @"some words").Index;

var pos0 = richTextBox1.GetPositionFromCharIndex(0).Y;
var pos = richTextBox1.GetPositionFromCharIndex(matchPos).Y + Math.Abs(pos0 - 1);

SendMessage(richTextBox1.Handle, WM_VSCROLL, pos << 16 | SB_THUMBPOSITION, 0);

ネイティブメソッド宣言:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, int lParam);

private const uint WM_VSCROLL = 0x0115;
private const int SB_THUMBPOSITION = 4;
2 TnTinMn Dec 10 2020 at 10:47

これを実現するには、TextBoxBase.ScrollToCaretメソッドに実装されているのと同じ方法を使用できます。この方法論は、基盤となるRichEditコントロールによって実装されたCOMベースのテキストオブジェクトモデルの使用に基づいています。

以下は、拡張メソッドを定義しScrollToCharPositionます。ITextDocumentおよびITextRangeインターフェイスの省略された定義を使用します。

public static class RTBExtensions
{

    public static void ScrollToCharPosition(this RichTextBox rtb, Int32 charPosition)
    {
        const Int32 WM_USER = 0x400;
        const Int32 EM_GETOLEINTERFACE = WM_USER + 60;
        const Int32 tomStart = 32;

        if (charPosition < 0 || charPosition > rtb.TextLength - 1)
        {
            throw new ArgumentOutOfRangeException(nameof(charPosition), $"{nameof(charPosition)} must be in the range of 0 to {rtb.TextLength - 1}.");
        }

        // retrieve the rtb's OLEINTERFACE and use the Interop Marshaller to cast it as an ITextDocument
        // The control calls the AddRef method for the object before returning, so the calling application must call the Release method when it is done with the object.
        ITextDocument doc = null;
        SendMessage(new HandleRef(rtb, rtb.Handle), EM_GETOLEINTERFACE, IntPtr.Zero, ref doc);
        ITextRange rng = null;
        if (doc != null)
        {
            try
            {
                rng = (RTBExtensions.ITextRange)doc.Range(charPosition, charPosition);
                rng.ScrollIntoView(tomStart);
            }
            finally
            {
                if (rng != null)
                {
                    Marshal.ReleaseComObject(rng);
                }
                Marshal.ReleaseComObject(doc);
            }
        }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private extern static IntPtr SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, ref ITextDocument lParam);

    [ComImport, Guid("8CC497C0-A1DF-11CE-8098-00AA0047BE5D")]
    private interface ITextDocument
    {
        [MethodImpl((short)0, MethodCodeType = MethodCodeType.Runtime)]
        void _VtblGap1_17();
        [return: MarshalAs(UnmanagedType.Interface)]
        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(15)]
        ITextRange Range([In] int cp1, [In] int cp2);
    }

    [ComImport, Guid("8CC497C2-A1DF-11CE-8098-00AA0047BE5D")]
    private interface ITextRange
    {
        [MethodImpl((short)0, MethodCodeType = MethodCodeType.Runtime)]
        void _VtblGap1_49();
        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x242)]
        void ScrollIntoView([In] int Value);
    }
}

使用例richtextbox1.ScrollToCharPosition(50)