如何防止在追加文本时 TextBox 自动滚动?

21

我有一个带有垂直滚动条的多行文本框,用于记录实时处理的数据。目前,每当通过 textBox.AppendText() 添加新行时,文本框会自动滚动到底部以便查看最后一项内容,这非常好。但是,我有一个复选框来指示是否允许文本框自动滚动。有没有办法做到这一点呢?

注意:

  • 我想使用文本框,因为添加的文本具有多行和空格对齐,所以使用 ListBox 或 ListView 不太简单。
  • 我尝试通过 textBox.Text += text 添加新行,但文本框不停地滚动到顶部。

如果我们有解决方案,那么另一个问题是如何防止当用户使用滚动条在文本框中浏览其他位置时,文本框自动滚动而不影响文本框追加文本呢?

private void OnTextLog(string text)
{
    if (chkAutoScroll.Checked)
    {
        // This always auto scrolls to the bottom.
        txtLog.AppendText(Environment.NewLine);
        txtLog.AppendText(text);

        // This always auto scrolls to the top.
        //txtLog.Text += Environment.NewLine + text;
    }
    else
    {
        // I want to append the text without scrolls right here.
    }
}

更新 1:正如saggio所建议的那样,我也认为解决此问题的方法是确定在将文本追加到并在此之后恢复的 TextBox 中显示的当前文本中第一个字符的位置。但怎么做呢?我尝试记录当前光标位置,像这样,但它并没有帮助:

int selpoint = txtLog.SelectionStart;
txtLog.AppendText(Environment.NewLine);
txtLog.AppendText(text);
txtLog.SelectionStart = selpoint;

更新2 (问题已解决):我在 Stack Overflow 上找到了一个解决方案,可以解决我的问题。我已经优化了他们的代码以适应我的情况,如下:

// Constants for extern calls to various scrollbar functions
private const int SB_VERT = 0x1;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 0x4;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
[DllImport("user32.dll")]
private static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos);

private void AppendTextToTextBox(TextBox textbox, string text, bool autoscroll)
{
    int savedVpos = GetScrollPos(textbox.Handle, SB_VERT);
    textbox.AppendText(text + Environment.NewLine);
    if (autoscroll)
    {
        int VSmin, VSmax;
        GetScrollRange(textbox.Handle, SB_VERT, out VSmin, out VSmax);
        int sbOffset = (int)((textbox.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight) / (textbox.Font.Height));
        savedVpos = VSmax - sbOffset;
    }
    SetScrollPos(textbox.Handle, SB_VERT, savedVpos, true);
    PostMessageA(textbox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
}

private void OnTextLog(string text)
{
    AppendTextToTextBox(txtLog.Text, Environment.NewLine + text, chkAutoScroll.Checked);
}

另一种方法:

private const int SB_VERT = 0x1;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 0x4;
private const int SB_BOTTOM = 0x7;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);

private void AppendTextToTextBox(TextBox textbox, string text, bool autoscroll)
{
    int savedVpos = GetScrollPos(textbox.Handle, SB_VERT);
    textbox.AppendText(text + Environment.NewLine);
    if (autoscroll)
    {
        PostMessageA(textbox.Handle, WM_VSCROLL, SB_BOTTOM, 0);
    }
    else
    {
        SetScrollPos(textbox.Handle, SB_VERT, savedVpos, true);
        PostMessageA(textbox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
    }
}

我发布这些解决方案是为了那些遇到相似问题的人。感谢cgyDeveloper的源代码。

有没有更加简单直接的方法?


5
如果在向TextBox追加文本之前记录光标位置,然后在追加文本后将其设置回该值,会怎样呢? - Saggio
如何记录当前光标位置?文本框的SelectionStart属性无法帮助:( - Huy Nguyen
你的意思是 textBox.SelectionStart 没有帮助吗?当你添加文本时,TextBox 是否具有焦点?如果没有,请尝试将 textBox.HideSelection 属性设置为 false - Saggio
这是WinForms还是WPF等? - LarsTech
以上代码用于WinForms :) - Huy Nguyen
显示剩余2条评论
3个回答

8

这似乎很简单,但我可能遗漏了一些东西。如果Autochecked为true,请使用append text滚动到该位置,如果您不想滚动,请仅添加文本。

更新...我确实遗漏了一些东西。您需要设置选择点,然后滚动到插入符号。请参见下面。

    if (chkAutoScroll.Checked)
    {
        // This always auto scrolls to the bottom.
        txtLog.AppendText(Environment.NewLine);
        txtLog.AppendText(text);

        // This always auto scrolls to the top.
        //txtLog.Text += Environment.NewLine + text;
    }
    else
    {
        int caretPos = txtLog.Text.Length;
        txtLog.Text += Environment.NewLine + text;
        txtLog.Select(caretPos, 0);            
        txtLog.ScrollToLine(txtLog.GetLineIndexFromCharacterIndex(caretPos));
    }

2
在缺少ScrollToCaret()的情况下,可以这样做:https://dev59.com/D2vXa4cB1Zd3GeqPJXUc - Nicolas Louis Guillemot
是的,这段代码在WPF中很有用。感谢您的贡献。 - Huy Nguyen
最终发现这是最好的方法:double offset = txtLog.VerticalOffset; txtLog.Text += Environment.NewLine + text; txtLog.ScrollToVerticalOffset(offset); - Nicolas Louis Guillemot
@NicolasLouisGuillemot 对于后来者来说,解释为什么Offset方法更好可能会有益处。 - Sorceri
使用 "int caretPos = txtLog.Text.Length" 将使滚动条始终移至文本底部。使用 "txtLog.VerticalOffset" 将使其停留在上一次离开的位置。 - Nicolas Louis Guillemot
显示剩余2条评论

0

期望的操作是:

  • 当滚动条被拖到底部时,打开自动滚动。
  • 当滚动条被拖到其他位置时,关闭自动滚动。

创建以下类:

    public class AutoScrollTextBox : TextBox
    {
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
            HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
        }

        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            bool isScrolledToEnd = VerticalOffset + ViewportHeight == ExtentHeight;
            base.OnTextChanged(e);
            CaretIndex = Text.Length;
            if (isScrolledToEnd)
            {
                ScrollToEnd();
            }                
        }
    }

将TextBox替换为AutoScrollTextBox,并在XML中将其附加到TextToDisplay绑定,以便在显示时添加内容

<local:AutoScrollTextBox Text="{Binding TextToDisplay }" />

0

你必须像这样做:

textBox1.AppendText("Your text here");
// this selects the index zero as the location of your caret
textBox1.Select(0, 0);
// Scrolls to the caret :)
textBox1.ScrollToCaret();

已在VS2010 c# Winforms上进行测试并正常工作,关于WPF我不确定,但是你可以通过谷歌来获取答案。


1
你的代码总是会使文本框滚动到顶部,这不是我想要的。我们讨论的问题是使文本框始终保持在当前位置,不要在添加新文本时滚动离开。 - Huy Nguyen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接