Подтвердить что ты не робот

Как показать полосы прокрутки в System.Windows.Forms.TextBox только тогда, когда текст не подходит?

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

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


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


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


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

4b9b3361

Ответ 1

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

  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

То, что описывает Aidan, - это почти точно сценарий пользовательского интерфейса, с которым я столкнулся. Поскольку текстовое поле доступно только для чтения, мне не нужно его отвечать на 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;
    }
}