2008-09-16 15 views
24

Para System.Windows.Forms.TextBox con Multiline = Verdadero, me gustaría mostrar solo las barras de desplazamiento cuando el texto no encaje.¿Cómo puedo mostrar las barras de desplazamiento en un System.Windows.Forms.TextBox solo cuando el texto no se ajusta?

Este es un cuadro de texto de solo lectura que se utiliza solo para la visualización. Es un TextBox para que los usuarios puedan copiar el texto. ¿Hay algo incorporado para admitir la exhibición automática de barras de desplazamiento? Si no, ¿debería usar un control diferente? ¿O tengo que enganchar TextChanged y comprobar manualmente si hay desbordamiento (si es así, cómo saber si el texto se ajusta?)


No tener un poco de suerte con varias combinaciones de ajustes WordWrap y barras de desplazamiento. Me gustaría no tener barras de desplazamiento inicialmente y que cada una de ellas aparezca dinámicamente solo si el texto no encaja en la dirección dada.


@nobugz, gracias, que funciona cuando WordWrap está deshabilitado. Preferiría no deshabilitar la conversión 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 cardinal para la intuición de UI.

Respuesta

13

Agregue una nueva clase a su proyecto y pegue el código que se muestra a continuación. Compilar. Coloque el nuevo control desde la parte superior de la caja de herramientas en su formulario. No es 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); 
    } 
} 
+4

Esto solo funciona si agrega líneas nuevas de manera explícita al texto. Si desea mostrar una larga línea de texto con el ajuste de palabras, este enfoque no funciona, ya que TextBox.Lines siempre será 1. Solo pensé en tirar eso, ya que ese era el caso que estaba buscando y esto no resolvió del todo el problema. – Tim

+0

Ya mencionado en el PO. Haga clic en el botón Preguntar para encontrar ayuda. –

7

También hice algunos experimentos, y encontré que la barra vertical siempre se mostrará si la habilita, y la barra horizontal siempre se muestra mientras esté habilitada y WordWrap == false.

Creo que no obtendrás exactamente lo que quieres aquí. Sin embargo, creo que a los usuarios les gustaría un mejor comportamiento predeterminado de Windows que el que intentas forzar. Si estuviese usando su aplicación, probablemente me molestaría si mis propiedades textbox se redujeran 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 el estilo de Windows.

31

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

La manera más fácil de hacerlo es cambiar a System.Windows.Forms.RichTextBox. La propiedad ScrollBars en este caso se puede dejar en el valor predeterminado de RichTextBoxScrollBars.Both, que indica "Mostrar una barra de desplazamiento horizontal y vertical cuando sea necesario". Sería bueno si esta funcionalidad se proporcionara en TextBox.

+2

+1: Simple, use RichTextBox en su lugar. Trabajó para mi. Gracias flojo – MattH

+6

Tenga en cuenta que RichTextBox está hecho para texto RTF y tiene un coste de procesamiento y procesamiento pesado en comparación con un cuadro de texto multilínea. Aconsejaría no usarlo a menos que desee mostrar texto enriquecido. – Camille

+2

He usado esta solución RichTextBox, sin efectos secundarios negativos. Creo que en la mayoría de los casos, la preocupación de Camille no está justificada. Gracias user73892. –

6

Hay un error extremadamente sutil en la solución de nobugz que da como resultado un daño en el montón, pero solo si está utilizando AppendText() para actualizar el TextBox.

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

Así que la moraleja de la historia es: no use AppendText() si va a utilizar la solución de nobugz.

+0

Tengo un problema de violación de acceso que parece relacionado con la visualización de un formulario con un TextBox (http://stackoverflow.com/q/7458915/68936). Después de ver tu publicación, pensé que podría deberse a llamar a AppendText(). Sin embargo, reemplacé la llamada a AppendText con solo TextBox.Text =, y la violación de acceso todavía ocurre de vez en cuando. De su respuesta, ¿debo entender que TextBox.Text = debería estar bien, y AppendText() no? ¿O ambos sufren el mismo problema (no llamarían ambos a OnTextChanged?) – Jimmy

2

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

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

Para la mayoría de las IU, los cuadros de texto con barras de desplazamiento vertical y horizontal son, bueno, malvados, así que aquí solo me interesan las barras de desplazamiento verticales.

También encontré que MeasureString produjo una altura que en realidad era más grande de lo que se requería. El uso de PreferredHeight del cuadro de texto sin borde como la 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; 
    } 
} 
Cuestiones relacionadas