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

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

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


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


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


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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
28
0
21 689
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

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

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

Ответ принят как подходящий

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

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

Это работает, только если вы явно добавляете в текст новые строки. Если вы хотите показать длинную строку текста с переносом слов, этот подход не работает, так как TextBox.Lines всегда будет 1. Просто подумал, что выкину это там, так как это был тот случай, который я искал, и это не совсем решило проблему.

Tim 14.03.2012 20:45

Уже упоминалось в ОП. Нажмите кнопку «Задать вопрос», чтобы получить помощь.

Hans Passant 14.03.2012 20:48

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

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

+1: просто, используйте вместо этого RichTextBox. Работал у меня. Спасибо слабое

MattH 29.09.2009 14:11

Имейте в виду, что RichTextBox создан для текста в формате RTF и требует больших затрат на рендеринг и обработку по сравнению с многострочным TextBox. Я бы не советовал использовать его, если вы не хотите отображать форматированный текст.

Camille 13.12.2011 04:00

Я использовал это решение RichTextBox, без побочных эффектов. Я считаю, что в большинстве случаев беспокойство Камиллы необоснованно. Спасибо user73892.

Radim Cernej 12.11.2013 22:27

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

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

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

У меня проблема с нарушением прав доступа, которая, похоже, связана с отображением формы с помощью TextBox (stackoverflow.com/q/7458915/68936). Увидев ваш пост, я подумал, что это могло быть из-за вызова AppendText (). Однако я заменил вызов AppendText только на TextBox.Text =, и нарушение прав доступа все еще возникает время от времени. Должен ли я понять из вашего ответа, что TextBox.Text = должен быть в порядке, а AppendText () - нет? Или они оба страдают от одной и той же проблемы (разве они оба не вызовут OnTextChanged?)

Jimmy 06.03.2012 22:30

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

  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 произвел высоту, которая на самом деле была больше, чем требовалось. Использование PreferredHeight текстового поля без границы, поскольку высота строки дает лучший результат.

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

Просто вызовите AutoScrollVertical (), когда вам это нужно, и при необходимости укажите 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;
    }
}

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