Cómo desplazar un control RichTextBox a un punto determinado independientemente de la posición del cursor

Dec 10 2020

En mi aplicación WinForms tengo un control RichTextBox que contiene un texto largo. Necesito desplazarlo programáticamente a un punto dado (expresado como un índice de caracteres), independientemente de dónde se encuentre el cursor de selección. Necesito un método como este:

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

Todas las respuestas a preguntas similares que he encontrado utilizan el ScrollToCaret()método, que está bien si desea desplazarse a la posición del cursor. Pero necesito desplazarme a una posición diferente en lugar del símbolo de intercalación, y sin cambiar la posición del símbolo de intercalación. ¿Cómo hago esto?

Gracias.

Respuestas

1 Jimi Dec 11 2020 at 04:09

Puede usar SendMessage para enviar un WM_VSCROLLmensaje al control RichEdit, especificando SB_THUMBPOSITIONen el LOWORDde wParamy la posición vertical absoluta para desplazarse en el HIWORD.

El método GetPositionFromCharIndex (pertenece a TextBoxBase , por lo que también se aplica a la clase TextBox) devuelve la posición física relativa donde se muestra un carácter en una posición específica (el valor puede ser negativo si la posición del carácter está por encima de la posición de desplazamiento actual y es la diferencia entre la posición de desplazamiento actual y la posición del carácter si está debajo de ella, a menos que la posición de desplazamiento actual sea 0).


Suponga que su RichTextBox se llama richTextBox1:

  • Utilice, por ejemplo, Regex.Match para determinar la posición de una palabra o frase; la posición del patrón coincidente es devuelta por la propiedad Index de Match.
  • Compruebe el desplazamiento actual con GetPositionFromCharIndex(0)
  • Agregue el valor absoluto del desplazamiento definido por la posición vertical actual, al valor, expresado en píxeles, devuelto por GetPositionFromCharIndex(matchPos), donde matchPoses la posición de un carácter / palabra / patrón para desplazarse.
  • Llame SendMessageusando la posición calculada y especificando mover el pulgar a esta posición pasando SB_THUMBPOSITIONcomo parte de 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);

Declaración de métodos nativos:

[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

Puede utilizar la misma metodología implementada en el método TextBoxBase.ScrollToCaret para lograr esto. Esta metodología se basa en el uso del modelo de objetos de texto basado en COM implementado por el control RichEdit subyacente.

Lo siguiente define el método de extensión ScrollToCharPosition. Utiliza definiciones abreviadas de las interfaces ITextDocument e 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);
    }
}

Ejemplo de uso :richtextbox1.ScrollToCharPosition(50)