Cómo desplazar un control RichTextBox a un punto determinado independientemente de la posición del cursor
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
Puede usar SendMessage para enviar un WM_VSCROLLmensaje al control RichEdit, especificando SB_THUMBPOSITION
en el LOWORD
de wParam
y 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)
, dondematchPos
es la posición de un carácter / palabra / patrón para desplazarse. - Llame
SendMessage
usando la posición calculada y especificando mover el pulgar a esta posición pasandoSB_THUMBPOSITION
como 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;
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)