캐럿 위치에 관계없이 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_THUMBPOSITIONwParam의 일부로 전달하는이 위치로 이동하도록 지정하여 호출 합니다 .
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)