Jak przewinąć kontrolkę RichTextBox do danego punktu niezależnie od pozycji daszka

Dec 10 2020

W mojej aplikacji WinForms mam kontrolkę RichTextBox zawierającą długi tekst. Muszę go programowo przewinąć do danego punktu (wyrażonego jako indeks znaków), niezależnie od tego, gdzie znajduje się daszek wyboru. Potrzebuję takiej metody:

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

Wszystkie odpowiedzi na podobne pytania, które znalazłem, używają ScrollToCaret()metody, która jest w porządku, jeśli chcesz przewinąć do pozycji daszka. Ale muszę przewinąć do innej pozycji, a nie do karetki i bez zmiany pozycji karetki. Jak mam to zrobic?

Dziękuję Ci.

Odpowiedzi

1 Jimi Dec 11 2020 at 04:09

Możesz użyć SendMessage, aby wysłać WM_VSCROLLwiadomość do kontrolki RichEdit, określając SB_THUMBPOSITIONw LOWORDof wParami bezwzględne położenie w pionie, do którego ma być przewijany plik HIWORD.

Metoda GetPositionFromCharIndex (należy do TextBoxBase , więc dotyczy również klasy TextBox) zwraca względną fizyczną pozycję, w której wyświetlany jest znak na określonej pozycji (wartość może być ujemna, jeśli pozycja znaku znajduje się powyżej bieżącej pozycji przewijania i jest różnica między bieżącą pozycją przewijania a pozycją znaku, jeśli znajduje się poniżej niej - chyba że bieżąca pozycja przewijania to 0).


Załóżmy, że Twój RichTextBox ma nazwę richTextBox1:

  • Użyj np. Regex.Match, aby określić położenie słowa lub frazy; pozycja dopasowanego wzorca jest zwracana przez właściwość Index dopasowania.
  • Sprawdź aktualne przesunięcie za pomocą GetPositionFromCharIndex(0)
  • Dodaj bezwzględną wartość przesunięcia zdefiniowanego przez bieżące położenie w pionie, do wartości - wyrażonej w pikselach - zwróconej przez GetPositionFromCharIndex(matchPos), gdzie matchPosjest pozycją znaku / słowa / wzorca do przewinięcia.
  • Wywołaj, SendMessageużywając obliczonej pozycji i określając, aby przesunąć kciuk do tej pozycji, przekazując SB_THUMBPOSITIONjako część 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);

Deklaracja metod natywnych:

[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

Aby to osiągnąć, możesz użyć tej samej metodologii zaimplementowanej w metodzie TextBoxBase.ScrollToCaret . Ta metodologia jest oparta na wykorzystaniu modelu obiektu tekstowego opartego na modelu COM zaimplementowanym przez podstawową kontrolkę RichEdit.

Poniżej zdefiniowano metodę rozszerzenia ScrollToCharPosition. Używa skróconych definicji interfejsów ITextDocument i 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);
    }
}

Przykładowe zastosowanie :richtextbox1.ScrollToCharPosition(50)