Как я могу показать полосы прокрутки на System.Windows.Forms.TextBox, только когда текст не помещается? - PullRequest
24 голосов
/ 16 сентября 2008

Для System.Windows.Forms.TextBox с Multiline = True я бы хотел показывать полосы прокрутки только тогда, когда текст не помещается.

Это текстовое поле только для чтения, используемое только для отображения. Это TextBox, так что пользователи могут копировать текст. Есть ли что-нибудь встроенное для поддержки автоматического показа полос прокрутки? Если нет, должен ли я использовать другой элемент управления? Или мне нужно перехватить TextChanged и вручную проверить на переполнение (если так, как определить, подходит ли текст?)

<Ч />

Не повезло с различными комбинациями настроек WordWrap и Scrollbars. Я бы хотел, чтобы изначально не было полос прокрутки, и чтобы каждая из них появлялась динамически, только если текст не соответствует указанному направлению.

<Ч />

@ nobugz, спасибо, это работает, когда WordWrap отключен. Я бы предпочел не отключать перенос слов, но это меньшее из двух зол.

<Ч />

@ Андре Невес, хорошая мысль, и я бы пошел по этому пути, если бы это было редактируемое пользователем. Я согласен, что последовательность является основным правилом интуитивности пользовательского интерфейса.

Ответы [ 6 ]

31 голосов
/ 04 марта 2009

Я сталкивался с этим вопросом, когда хотел решить ту же проблему.

Самый простой способ сделать это - перейти на System.Windows.Forms.RichTextBox. Свойство ScrollBars в этом случае можно оставить равным значению по умолчанию RichTextBoxScrollBars.Both, которое указывает «Отображать горизонтальную и вертикальную полосу прокрутки, когда это необходимо». Было бы хорошо, если бы эта функциональность была предоставлена ​​на TextBox.

14 голосов
/ 18 сентября 2008

Добавьте новый класс в ваш проект и вставьте код, показанный ниже. Компиляция. Перетащите новый элемент управления из верхней части панели инструментов на форму. Это не совсем идеально, но должно работать для вас.

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);
  }
}
7 голосов
/ 16 сентября 2008

Я также провел несколько экспериментов и обнаружил, что вертикальная полоса всегда будет отображаться, если вы ее включите, а горизонтальная полоса всегда будет отображаться, пока она включена и WordWrap == false.

Я думаю, вы не получите здесь то, что хотите. Однако я считаю, что пользователям хотелось бы, чтобы поведение Windows по умолчанию было лучше, чем то, которое вы пытаетесь заставить. Если бы я использовал ваше приложение, я бы, наверное, был бы обеспокоен, если бы объем моего текстового поля внезапно уменьшился только потому, что в нем должна быть неожиданная полоса прокрутки, потому что я выдал слишком много текста!

Возможно, было бы неплохо, если бы ваше приложение следовало стилю Windows.

6 голосов
/ 20 марта 2009

В решении nobugz есть очень тонкая ошибка, которая приводит к повреждению кучи, но только если вы используете AppendText () для обновления TextBox.

Установка свойства ScrollBars из OnTextChanged приведет к разрушению и воссозданию окна Win32 (дескриптор). Но OnTextChanged вызывается из недр элемента управления для редактирования Win32 (EditML_InsertText), который сразу после этого ожидает, что внутреннее состояние этого элемента управления для редактирования Win32 не изменится. К сожалению, поскольку окно воссоздано, это внутреннее состояние было освобождено ОС, что привело к нарушению доступа.

Итак, мораль этой истории такова: не используйте AppendText (), если вы собираетесь использовать решение nobugz.

2 голосов
/ 10 ноября 2010

У меня был некоторый успех с кодом ниже.

  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);
    }
  }
0 голосов
/ 08 августа 2014

То, что описывает Эйдан, является почти точно сценарием пользовательского интерфейса, с которым я сталкиваюсь. Поскольку текстовое поле доступно только для чтения, мне не нужно, чтобы оно отвечало на TextChanged. И я бы предпочел, чтобы пересчет автопрокрутки был отложен, чтобы он не срабатывал десятки раз в секунду во время изменения размера окна.

Для большинства пользовательских интерфейсов текстовые поля с вертикальной и горизонтальной полосами прокрутки, ну, в общем, зло, поэтому меня интересуют только вертикальные полосы прокрутки здесь.

Я также обнаружил, что 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;
    }
}
...