【问题标题】:How to scroll a RichTextBox control to a given point regardless of caret position无论插入符号位置如何,如何将 RichTextBox 控件滚动到给定点
【发布时间】:2020-12-09 18:41:50
【问题描述】:

在我的 WinForms 应用程序中,我有一个包含长文本的 RichTextBox 控件。无论选择插入符号位于何处,我都需要以编程方式将其滚动到给定点(表示为字符索引)。我需要这样的方法:

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

我发现的所有类似问题的答案都使用ScrollToCaret() 方法,如果您想滚动到插入符号位置,这很好。但我需要滚动到不同的位置而不是插入符号,并且不改变插入符号的位置。我该怎么做?

谢谢。

【问题讨论】:

  • 你试过了吗int s = rtb.SelectionStart; rtb.SuspendLayout(); rtb.SelectionStart = 12334; rtb.ScrollToCaret(); rtb.SelectionStart = s; rtb.ResumeLayout();
  • 这能回答你的问题吗? Change scrollbar position in TextBox?

标签: c# winforms richtextbox


【解决方案1】:

您可以使用TextBoxBase.ScrollToCaret Method 中实施的相同方法来完成此操作。此方法基于使用底层 RichEdit 控件实现的基于 COM 的Text Object Model

下面定义了扩展方法ScrollToCharPosition。它使用ITextDocumentITextRange 接口的缩写定义。

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)

【讨论】:

  • 谢谢。我将首先尝试 cmets 中建议的方法,因为它们看起来更简单。如果它们不起作用,我会尝试你的方法。
  • @CesarGon,如果您尝试建议的WM_VSCROLL /GetPositionFromCharIndex,请注意这些仅限于使用 16 位坐标值。根据字体高度,你会在几千行之后遇到问题。这对您的使用可能不是问题,但您应该注意限制。还要意识到GetPositionFromCharIndex 可以返回负值,因此您也需要适应这种情况。祝你好运。
【解决方案2】:

您可以使用SendMessage 向RichEdit 控件发送WM_VSCROLL 消息,在wParamLOWORD 中指定SB_THUMBPOSITION,并在HIWORD 中指定要滚动到的绝对垂直位置。

GetPositionFromCharIndex 方法(属于TextBoxBase,因此也适用于 TextBox 类)返回显示特定位置的字符的相对物理位置(该值可以如果 char 位置高于当前滚动位置,则为负数,并且它是当前滚动位置与低于它的 char 位置之间的差异 - 除非当前滚动位置是 0)。


假设您的 RichTextBox 名为 richTextBox1

  • 使用例如Regex.Match 来确定单词或短语的位置;匹配模式的位置由 Match 的 Index 属性返回。
  • 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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多