代码之家  ›  专栏  ›  技术社区  ›  Aidan Ryan

仅当文本不适合时,如何在System.Windows.Forms.TextBox上显示滚动条?

  •  24
  • Aidan Ryan  · 技术社区  · 16 年前

    对于multiline=true的system.windows.forms.textbox,我只想在文本不合适时显示滚动条。

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


    在自动换行和滚动条设置的各种组合中没有任何运气。我希望最初没有滚动条,只有当文本不适合给定的方向时,才会动态显示每个滚动条。


    @Nobugz,谢谢,禁用自动换行时可以。我不想禁用自动换行,但这是两种邪恶中的较小的一种。


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

    6 回复  |  直到 10 年前
        1
  •  14
  •   Hans Passant    13 年前

    向项目中添加新类并粘贴下面显示的代码。编译。将新控件从工具箱顶部放到窗体上。它不太完美,但应该适合你。

    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);
      }
    }
    
        2
  •  31
  •   user73892    16 年前

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

    最简单的方法是更改为System.Windows.Forms.RichTextBox。在这种情况下,ScrollBars属性可以保留为RichTextBoxScrollBars的默认值。两者都表示“需要时同时显示水平滚动条和垂直滚动条”。如果在文本框上提供此功能,那就更好了。

        3
  •  7
  •   André Chalella    16 年前

    我还做了一些实验,发现如果启用垂直条,它将始终显示,而水平条则始终显示,只要启用和 WordWrap == false .

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

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

        4
  •  6
  •   BKewl    16 年前

    NoBugz的解决方案中有一个非常微妙的bug,它会导致堆损坏,但前提是使用AppendText()更新文本框。

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

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

        5
  •  2
  •   b8adamson    14 年前

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

      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);
        }
      }
    
        6
  •  0
  •   Aidan Ryan    10 年前

    Aidan所描述的几乎就是我所面对的UI场景。因为文本框是只读的,所以我不需要它来响应TextChanged。我更喜欢自动滚动重新计算被延迟,这样在调整窗口大小时,它不会每秒触发几十次。

    对于大多数UI来说,带有垂直和水平滚动条的文本框都很糟糕,所以我只对垂直滚动条感兴趣。

    我还发现测量产生的高度实际上比要求的要大。使用文本框的preferredheight(不带边框)作为行高可以得到更好的结果。

    下面的内容似乎很好地工作,不管有没有边框,它都适用于自动换行。

    只需在需要时调用autoscrollVertical(),并可以选择指定RecalculationResize。

    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;
        }
    }