¿Cómo puedo mostrar barras de desplazamiento en System.Windows.Forms.TextBox solo cuando el texto no cabe?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Para System.Windows.Forms.TextBox con Multiline=True, me gustaría mostrar las barras de desplazamiento solo cuando el texto no cabe.

Este es un cuadro de texto de solo lectura que se utiliza únicamente para visualización.Es un TextBox para que los usuarios puedan copiar el texto.¿Hay algo integrado para admitir la visualización automática de barras de desplazamiento?Si no, ¿debería utilizar un control diferente?¿O necesito conectar TextChanged y verificar manualmente si hay desbordamiento (si es así, cómo saber si el texto encaja?)


No tener suerte con varias combinaciones de configuraciones de WordWrap y barras de desplazamiento.Inicialmente, me gustaría no tener barras de desplazamiento y que cada una aparezca dinámicamente solo si el texto no encaja en la dirección dada.


@nobugz, gracias, eso funciona cuando WordWrap está deshabilitado.Preferiría no desactivar el ajuste de palabras, pero es el menor de dos males.


@André Neves, buen punto, y yo iría por ese camino si fuera editable por el usuario.Estoy de acuerdo en que la coherencia es la regla fundamental para la intuición de la interfaz de usuario.

¿Fue útil?

Solución

Agregue una nueva clase a su proyecto y pegue el código que se muestra a continuación.Compilar.Suelte el nuevo control desde la parte superior de la caja de herramientas en su formulario.No es del todo perfecto, pero debería funcionar para ti.

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

Otros consejos

Me encontré con esta pregunta cuando quería resolver el mismo problema.

La forma más sencilla de hacerlo es cambiar a System.Windows.Forms.RichTextBox.La propiedad ScrollBars en este caso se puede dejar al valor predeterminado de RichTextBoxScrollBars.both, que indica "Muestra una barra de desplazamiento horizontal y vertical cuando sea necesario". Sería bueno si esta funcionalidad se proporcionara en el cuadro de texto.

También hice algunos experimentos y descubrí que la barra vertical siempre se mostrará si la habilitas, y la barra horizontal siempre se mostrará siempre que esté habilitada y WordWrap == false.

Creo que aquí no obtendrás exactamente lo que quieres.Sin embargo, creo que a los usuarios les gustaría un mejor comportamiento predeterminado de Windows que el que usted está intentando forzar.Si estuviera usando su aplicación, probablemente me molestaría si el espacio de mi cuadro de texto se redujera repentinamente solo porque necesita acomodar una barra de desplazamiento inesperada porque le di demasiado texto.

Quizás sería una buena idea dejar que su aplicación siga la apariencia de Windows.

Hay un error extremadamente sutil en la solución de nobugz que resulta en una corrupción del montón, pero solo si estás usando AppendText() para actualizar el TextBox.

Establecer la propiedad ScrollBars desde OnTextChanged hará que la ventana (identificador) de Win32 se destruya y se vuelva a crear.Pero OnTextChanged se llama desde las entrañas del control de edición Win32 (EditML_InsertText), que inmediatamente después espera que el estado interno de ese control de edición Win32 no cambie.Desafortunadamente, desde que se volvió a crear la ventana, el sistema operativo liberó ese estado interno, lo que resultó en una infracción de acceso.

Entonces la moraleja de la historia es:No uses AppendText() si vas a usar la solución de nobugz.

Tuve cierto éxito con el siguiente código.

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

Lo que Aidan describe es casi exactamente el escenario de UI al que me enfrento.Como el cuadro de texto es de solo lectura, no lo necesito para responder a TextChanged.Y preferiría que el recálculo del desplazamiento automático se retrase para que no se active docenas de veces por segundo mientras se cambia el tamaño de una ventana.

Para la mayoría de las interfaces de usuario, los cuadros de texto con barras de desplazamiento verticales y horizontales son, bueno, malvados, por lo que aquí solo me interesan las barras de desplazamiento verticales.

También descubrí que MeasureString producía una altura que en realidad era mayor de lo requerido.Usar PreferredHeight del cuadro de texto sin borde como altura de línea da un mejor resultado.

Lo siguiente parece funcionar bastante bien, con o sin borde, y funciona con WordWrap activado.

Simplemente llame a AutoScrollVertically() cuando lo necesite y, opcionalmente, especifique 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;
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top