Как прокрутить элемент управления RichTextBox до заданной точки независимо от положения курсора
В моем приложении WinForms у меня есть элемент управления RichTextBox, содержащий длинный текст. Мне нужно программно прокрутить его до заданной точки (выраженной индексом символа), независимо от того, где находится курсор выбора. Мне нужен такой метод:
//scroll the control so that the 3512th character is visible.
rtf.ScrollToPosition(3512);
Все ответы на подобные вопросы, которые я нашел, используют этот ScrollToCaret()
метод, что нормально, если вы хотите прокрутить до позиции курсора. Но мне нужно прокрутить до другого положения, а не курсора, и без изменения положения курсора. Как мне это сделать?
Спасибо.
Ответы
Вы можете использовать SendMessage , чтобы отправить WM_VSCROLLсообщение в RichEdit контроля, с указанием SB_THUMBPOSITION
в LOWORD
о wParam
и абсолютном вертикальном положении для прокрутки в HIWORD
.
Метод GetPositionFromCharIndex (принадлежит TextBoxBase , поэтому он применяется и к классу TextBox) возвращает относительную физическую позицию, в которой отображается символ в определенной позиции (значение может быть отрицательным, если положение символа выше текущей позиции прокрутки и оно разница между текущей позицией прокрутки и позицией символа, если она ниже - если текущая позиция прокрутки не равна 0
).
Предположим, ваш RichTextBox назван richTextBox1
:
- Используйте, например, Regex.Match, чтобы определить позицию слова или фразы; положение совпадающего шаблона возвращается свойством Index объекта Match.
- Проверьте текущее смещение с помощью
GetPositionFromCharIndex(0)
- Добавьте абсолютное значение смещения, определяемого текущей вертикальной позицией, к значению, выраженному в пикселях, которое возвращает
GetPositionFromCharIndex(matchPos)
, гдеmatchPos
- позиция символа / слова / шаблона для прокрутки. - Вызов,
SendMessage
используя вычисленную позицию и указав, чтобы переместить ползунок в эту позицию, передаваяSB_THUMBPOSITION
как часть 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);
Объявление собственных методов:
[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;
Для этого можно использовать ту же методологию, что и в методе TextBoxBase.ScrollToCaret . Эта методология основана на использовании модели текстовых объектов на основе COM, реализованной базовым элементом управления RichEdit.
Следующее определяет метод расширения 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)