Cara menggulir kontrol RichTextBox ke titik tertentu terlepas dari posisi sisipan

Dec 10 2020

Di aplikasi WinForms saya, saya memiliki kontrol RichTextBox yang berisi teks panjang. Saya perlu menggulirnya secara terprogram ke titik tertentu (dinyatakan sebagai indeks karakter), terlepas dari di mana tanda sisipan pemilihan berada. Saya butuh metode seperti ini:

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

Semua jawaban atas pertanyaan serupa yang saya temukan menggunakan ScrollToCaret()metode ini, yang tidak masalah jika Anda ingin menggulir ke posisi sisipan. Tetapi saya perlu menggulir ke posisi yang berbeda daripada tanda sisipan, dan tanpa mengubah posisi tanda sisipan. Bagaimana saya melakukan ini?

Terima kasih.

Jawaban

1 Jimi Dec 11 2020 at 04:09

Anda dapat menggunakan SendMessage untuk mengirim WM_VSCROLLpesan ke kontrol RichEdit, menentukan SB_THUMBPOSITIONdi LOWORDdari wParamdan posisi vertikal absolut untuk menggulir ke dalam HIWORD.

The GetPositionFromCharIndex metode (milik TextBoxBase , sehingga berlaku untuk kelas TextBox, juga) mengembalikan relatif fisik posisi di mana char dalam posisi tertentu ditampilkan (nilai bisa menjadi negatif jika posisi char di atas posisi gulir saat ini dan itu perbedaan antara posisi gulir saat ini dan posisi karakter jika di bawahnya - kecuali posisi gulir saat ini 0).


Asumsikan RichTextBox Anda diberi nama richTextBox1:

  • Gunakan, misalnya, Regex.Match untuk menentukan posisi kata atau frase; posisi berpola yang cocok dikembalikan oleh properti Indeks Pertandingan.
  • Periksa offset saat ini dengan GetPositionFromCharIndex(0)
  • Tambahkan nilai absolut dari offset yang ditentukan oleh posisi vertikal saat ini, ke nilai - yang dinyatakan dalam piksel - yang dikembalikan oleh GetPositionFromCharIndex(matchPos), di mana matchPosposisi karakter / kata / pola untuk digulir.
  • Panggil SendMessagemenggunakan posisi yang dihitung dan tentukan untuk memindahkan ibu jari ke posisi ini lewat SB_THUMBPOSITIONsebagai bagian dari 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);

Deklarasi metode asli:

[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

Anda dapat menggunakan metodologi yang sama yang diterapkan dalam Metode TextBoxBase.ScrollToCaret untuk melakukannya. Metodologi ini didasarkan pada penggunaan Model Objek Teks berbasis COM yang diimplementasikan oleh kontrol RichEdit yang mendasari.

Berikut ini adalah definisi metode ekstensi ScrollToCharPosition. Ini menggunakan disingkat definisi dari ITextDocument dan ITextRange interface.

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);
    }
}

Contoh Penggunaan :richtextbox1.ScrollToCharPosition(50)