Как я могу показать полосы прокрутки в Системе?Windows.Формы.Текстовое поле только тогда, когда текст не помещается?

StackOverflow https://stackoverflow.com/questions/73110

  •  09-06-2019
  •  | 
  •  

Вопрос

Для Системы.Windows.Формы.Текстовое поле с многострочностью = True, я бы хотел показывать полосы прокрутки только тогда, когда текст не помещается.

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


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


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


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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

Я также обнаружил, что MeasureString выдает высоту, которая на самом деле больше, чем требовалось.Использование предпочтительной высоты текстового поля без границы в качестве высоты строки дает лучший результат.

Следующее, кажется, работает довольно хорошо, с рамкой или без нее, и оно работает с включенным WordWrap.

Просто вызовите AutoScrollVertically(), когда вам это нужно, и при необходимости укажите recallulateOnResize .

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;
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top