28

对于 Multiline=True 的 System.Windows.Forms.TextBox,我只想在文本不适合时显示滚动条。

这是一个只读文本框,仅用于显示。它是一个文本框,以便用户可以将文本复制出来。是否有内置支持滚动条自动显示的功能?如果不是,我应该使用不同的控件吗?还是我需要挂钩 TextChanged 并手动检查溢出(如果是,如何判断文本是否合适?)


对 WordWrap 和 Scrollbars 设置的各种组合没有任何运气。我希望最初没有滚动条,并且只有在文本不适合给定方向时才动态显示每个滚动条。


@nobugz,谢谢,在禁用 WordWrap 时有效。我不想禁用自动换行,但这是两害相权取其轻。


@André Neves,好点子,如果它是用户可编辑的,我会这样做。我同意一致性是 UI 直观性的基本规则。

4

6 回答 6

33

当我想解决同样的问题时,我遇到了这个问题。

最简单的方法是更改​​为 System.Windows.Forms.RichTextBox。本例中的 ScrollBars 属性可以保留为 RichTextBoxScrollBars.Both 的默认值,表示“在需要时同时显示水平和垂直滚动条”。如果在 TextBox 上提供此功能,那就太好了。

于 2009-03-04T20:15:29.410 回答
14

向您的项目添加一个新类并粘贴如下所示的代码。编译。将新控件从工具箱顶部拖放到表单上。它不是很完美,但应该适合你。

using System;
using System.Drawing;
using System.Windows.Forms;

public class MyTextBox : TextBox {
  private bool mScrollbars;
  public MyTextBox() {
    this.Multiline = true;
    this.ReadOnly = true;
  }
  private void checkForScrollbars() {
    bool scroll = false;
    int cnt = this.Lines.Length;
    if (cnt > 1) {
      int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
      if (pos0 >= 32768) pos0 -= 65536;
      int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
      if (pos1 >= 32768) pos1 -= 65536;
      int h = pos1 - pos0;
      scroll = cnt * h > (this.ClientSize.Height - 6);  // 6 = padding
    }
    if (scroll != mScrollbars) {
      mScrollbars = scroll;
      this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
    }
  }

  protected override void OnTextChanged(EventArgs e) {
    checkForScrollbars();
    base.OnTextChanged(e);
  }

  protected override void OnClientSizeChanged(EventArgs e) {
    checkForScrollbars();
    base.OnClientSizeChanged(e);
  }
}
于 2008-09-18T02:18:22.637 回答
7

我也做了一些实验,发现启用后竖条一直显示,而横条只要启用就一直显示WordWrap == false

我认为你不会在这里得到你想要的东西。但是,我相信用户希望 Windows 的默认行为比您试图强制的更好。如果我使用的是你的应用程序,如果我的文本框空间突然缩小,我可能会感到困扰,因为它需要容纳一个意外的滚动条,因为我给了它太多的文本!

也许让您的应用程序遵循 Windows 的外观和感觉是个好主意。

于 2008-09-16T14:51:21.360 回答
7

nobugz 的解决方案中有一个非常微妙的错误会导致堆损坏,但前提是您使用 AppendText() 来更新 TextBox。

从 OnTextChanged 设置 ScrollBars 属性将导致 Win32 窗口(句柄)被销毁并重新创建。但是 OnTextChanged 是从 Win32 编辑控件 (EditML_InsertText) 的内部调用的,此后它立即期望该 Win32 编辑控件的内部状态保持不变。不幸的是,由于重新创建了窗口,操作系统已释放该内部状态,从而导致访问冲突。

所以这个故事的寓意是:如果您要使用 nobugz 的解决方案,请不要使用 AppendText()。

于 2009-03-20T15:47:12.557 回答
2

我用下面的代码取得了一些成功。

  public partial class MyTextBox : TextBox
  {
    private bool mShowScrollBar = false;

    public MyTextBox()
    {
      InitializeComponent();

      checkForScrollbars();
    }

    private void checkForScrollbars()
    {
      bool showScrollBar = false;
      int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;

      using (Graphics g = this.CreateGraphics())
      {
        // Calcualte the size of the text area.
        SizeF textArea = g.MeasureString(this.Text,
                                         this.Font,
                                         this.Bounds.Width - padding);

        if (this.Text.EndsWith(Environment.NewLine))
        {
          // Include the height of a trailing new line in the height calculation        
          textArea.Height += g.MeasureString("A", this.Font).Height;
        }

        // Show the vertical ScrollBar if the text area
        // is taller than the control.
        showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));

        if (showScrollBar != mShowScrollBar)
        {
          mShowScrollBar = showScrollBar;
          this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
        }
      }
    }

    protected override void OnTextChanged(EventArgs e)
    {
      checkForScrollbars();
      base.OnTextChanged(e);
    }

    protected override void OnResize(EventArgs e)
    {
      checkForScrollbars();
      base.OnResize(e);
    }
  }
于 2010-11-10T17:42:57.190 回答
0

Aidan 所描述的几乎正是我所面临的 UI 场景。由于文本框是只读的,我不需要它来响应 TextChanged。而且我更希望延迟自动滚动重新计算,这样在调整窗口大小时它不会每秒触发数十次。

对于大多数 UI,带有垂直和水平滚动条的文本框是邪恶的,所以我只对垂直滚动条感兴趣。

我还发现 MeasureString 产生的高度实际上比要求的要大。使用没有边框的文本框的 PreferredHeight 作为行高可以得到更好的结果。

以下似乎工作得很好,有或没有边框,它适用于 WordWrap。

只需在需要时调用 AutoScrollVertically(),并可选择指定 recalculateOnResize。

public class TextBoxAutoScroll : TextBox
{
    public void AutoScrollVertically(bool recalculateOnResize = false)
    {
        SuspendLayout();

        if (recalculateOnResize)
        {
            Resize -= OnResize;
            Resize += OnResize;
        }

        float linesHeight = 0;
        var   borderStyle = BorderStyle;

        BorderStyle       = BorderStyle.None;

        int textHeight    = PreferredHeight;

        try
        {
            using (var graphics = CreateGraphics())
            {
                foreach (var text in Lines)
                {
                    var textArea = graphics.MeasureString(text, Font);

                    if (textArea.Width < Width)
                        linesHeight += textHeight;
                    else
                    {
                        var numLines = (float)Math.Ceiling(textArea.Width / Width);

                        linesHeight += textHeight * numLines;
                    }
                }
            }

            if (linesHeight > Height)
                ScrollBars = ScrollBars.Vertical;
            else
                ScrollBars = ScrollBars.None;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
        }
        finally
        {
            BorderStyle = borderStyle;

            ResumeLayout();
        }
    }

    private void OnResize(object sender, EventArgs e)
    {
        m_timerResize.Stop();

        m_timerResize.Tick    -= OnDelayedResize;
        m_timerResize.Tick    += OnDelayedResize;
        m_timerResize.Interval = 475;

        m_timerResize.Start();
    }

    Timer m_timerResize = new Timer();

    private void OnDelayedResize(object sender, EventArgs e)
    {
        m_timerResize.Stop();

        Resize -= OnResize;

        AutoScrollVertically();

        Resize += OnResize;
    }
}
于 2014-08-08T07:09:09.690 回答