¿Cómo me desplazo automáticamente a la parte inferior de un cuadro de texto multilínea?

295

Tengo un cuadro de texto con la propiedad .Multiline establecida en true. A intervalos regulares, le agrego nuevas líneas de texto. Me gustaría que el cuadro de texto se desplace automáticamente a la entrada más inferior (la más nueva) cada vez que se agrega una nueva línea. ¿Cómo logro esto?

GWLlosa
fuente
66
Busqué la respuesta aquí, no pude encontrarla, así que cuando lo descubrí, pensé que lo pondría aquí para futuros usuarios, o si tal vez alguien más tuviera un mejor enfoque.
GWLlosa
2
Necesitaba hacer lo mismo en VBA, que no tiene todos estos nuevos y sofisticados métodos .NET. Para el futuro google-fu, aquí está el encantamiento: TextBox1.Text = TextBox1.Text & "whatever"; TextBox1.SelStart = Len (TextBox1.Text); TextBox1.SetFocus; ... y luego un .SetFocus de vuelta a cualquier control que tuviera el foco antes. Sin darle el foco a TextBox1, nunca actualizaría sus barras de desplazamiento sin importar lo que hice.
Gordon Broom
1
@GordonBroom Whelp, gracias a eso voy a comenzar a llamar "fragmentos de código" "encantamientos" ahora. Buen trabajo. : D
Sidney

Respuestas:

425

A intervalos regulares, le agrego nuevas líneas de texto. Me gustaría que el cuadro de texto se desplace automáticamente a la entrada más inferior (la más nueva) cada vez que se agrega una nueva línea.

Si lo usa TextBox.AppendText(string text), se desplazará automáticamente hasta el final del texto recién agregado. Evita la barra de desplazamiento parpadeante si la está llamando en un bucle.

También resulta ser un orden de magnitud más rápido que concatenar en la .Textpropiedad. Aunque eso podría depender de con qué frecuencia lo estás llamando; Estaba probando con un circuito cerrado.


Esto no se desplazará si se llama antes de que se muestre el cuadro de texto, o si el cuadro de texto no está visible (por ejemplo, en una pestaña diferente de un Panel de pestañas). Consulte TextBox.AppendText () sin desplazamiento automático . Esto puede o no ser importante, dependiendo de si necesita desplazamiento automático cuando el usuario no puede ver el cuadro de texto.

Parece que el método alternativo de las otras respuestas tampoco funciona en este caso. Una forma de evitarlo es realizar un desplazamiento adicional en el VisibleChangedevento:

textBox.VisibleChanged += (sender, e) =>
{
    if (textBox.Visible)
    {
        textBox.SelectionStart = textBox.TextLength;
        textBox.ScrollToCaret();
    }
};

Internamente, AppendTexthace algo como esto:

textBox.Select(textBox.TextLength + 1, 0);
textBox.SelectedText = textToAppend;

Pero no debería haber ninguna razón para hacerlo manualmente.

(Si lo descompila usted mismo, verá que usa algunos métodos internos posiblemente más eficientes, y tiene lo que parece ser un caso especial menor).

Beto
fuente
77
Este método es mucho más rápido y suave. No hay "parpadeo" de la barra de desplazamiento (que es más notable cuando se hacen muchas llamadas en rápida sucesión).
TallGuy
3
Esta es una solución mucho mejor.
Jeff
3
Estaba comiendo tratando de hacerlo con tb.Text += ....WndProc y mariscales. Ahora me siento estúpido: D
Saeid Yazdani
3
El área de texto también debe enfocarse, la primera vez que hice esto no se desplazó porque no tenía el foco.
Qwerty01
3
AppendText no desplazó automáticamente mi ReadOnly TextBox, sino que agregó TextBox.ScrollToEnd (); después de que la llamada AppendText hiciera el truco.
Brandon Barkley
143

Puede usar el siguiente fragmento de código:

myTextBox.SelectionStart = myTextBox.Text.Length;
myTextBox.ScrollToCaret();

que se desplazará automáticamente hasta el final.

GWLlosa
fuente
55
Busqué la respuesta aquí, no pude encontrarla, así que cuando lo descubrí, pensé que lo pondría aquí para futuros usuarios, o si tal vez alguien más tuviera un mejor enfoque.
GWLlosa
44
Esta puede haber sido la mejor respuesta en ese momento, pero ahora creo que la respuesta de Bob es una mejor solución al problema del OP.
tomsv
38

Parece que la interfaz ha cambiado en .NET 4.0. Existe el siguiente método que logra todo lo anterior. Como sugirió Tommy Engebretsen, ponerlo en un controlador de eventos TextChanged lo hace automático.

textBox1.ScrollToEnd();
JohnDRoach
fuente
21
Tenga en cuenta que ese método está en la TextBoxBaseclase en el System.Windows.Controls.Primitivesespacio de nombres ( PresentationFrameworkensamblaje, WPF). Este método no existe y no funcionará en Windows Forms, cuya TextBoxhereda la clase de TextBoxBaseen el System.Windows.Formsespacio de nombres ( System.Windows.Formsde ensamblaje, WinForms).
Bob
1
Tenga en cuenta que ScrollToEnd()puede tener un rendimiento extremadamente bajo. En mi aplicación representaba más del 50% del tiempo de creación de perfiles.
ergohack
16

Intente agregar el código sugerido al evento TextChanged:

private void textBox1_TextChanged(object sender, EventArgs e)
{
  textBox1.SelectionStart = textBox1.Text.Length;
  textBox1.ScrollToCaret();
}
GWLlosa
fuente
10
textBox1.Focus()
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();

no funcionó para mí (Windows 8.1, sea cual sea el motivo).
Y como todavía estoy en .NET 2.0, no puedo usar ScrollToEnd.

Pero esto funciona:

public class Utils
{
    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    private static extern int SendMessage(System.IntPtr hWnd, int wMsg, System.IntPtr wParam, System.IntPtr lParam);

    private const int WM_VSCROLL = 0x115;
    private const int SB_BOTTOM = 7;

    /// <summary>
    /// Scrolls the vertical scroll bar of a multi-line text box to the bottom.
    /// </summary>
    /// <param name="tb">The text box to scroll</param>
    public static void ScrollToBottom(System.Windows.Forms.TextBox tb)
    {
        if(System.Environment.OSVersion.Platform != System.PlatformID.Unix)
             SendMessage(tb.Handle, WM_VSCROLL, new System.IntPtr(SB_BOTTOM), System.IntPtr.Zero);
    }


}

VB.NET:

Public Class Utils
    <System.Runtime.InteropServices.DllImport("user32.dll", CharSet := System.Runtime.InteropServices.CharSet.Auto)> _
    Private Shared Function SendMessage(hWnd As System.IntPtr, wMsg As Integer, wParam As System.IntPtr, lParam As System.IntPtr) As Integer
    End Function

    Private Const WM_VSCROLL As Integer = &H115
    Private Const SB_BOTTOM As Integer = 7

    ''' <summary>
    ''' Scrolls the vertical scroll bar of a multi-line text box to the bottom.
    ''' </summary>
    ''' <param name="tb">The text box to scroll</param>
    Public Shared Sub ScrollToBottom(tb As System.Windows.Forms.TextBox)
        If System.Environment.OSVersion.Platform <> System.PlatformID.Unix Then
            SendMessage(tb.Handle, WM_VSCROLL, New System.IntPtr(SB_BOTTOM), System.IntPtr.Zero)
        End If
    End Sub


End Class
Stefan Steiger
fuente
Tuvo el mismo problema con Windows 10, su solución funciona bien aquí también.
Hannes
Ninguno estaba funcionando para mí, pero esto. Dios bendiga tu alma
Emirhan Özlen
9

Necesitaba agregar una actualización:

textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox1.Refresh();
h4nd
fuente
4

Encontré una diferencia simple que no se ha abordado en este hilo.

Si está haciendo todas las ScrollToCarat()llamadas como parte del Load()evento de su formulario , no funciona. Acabo de agregar mi ScrollToCarat()llamada al Activated()evento de mi formulario , y funciona bien.

Editar

Es importante hacer este desplazamiento solo la primera vez que se activa el Activatedevento del formulario (no en activaciones posteriores), o se desplazará cada vez que se active su formulario, que es algo que probablemente no desee.

Entonces, si solo está atrapando el Activated()evento para desplazar su texto cuando se carga su programa, entonces simplemente puede darse de baja del evento dentro del controlador de eventos, por lo tanto:

Activated -= new System.EventHandler(this.Form1_Activated);

Si tiene otras cosas que debe hacer cada vez que se activa su formulario, puede establecer un valor boolverdadero la primera vez que se activa su Activated()evento, por lo que no se desplazará en las activaciones posteriores, pero aún puede hacer las otras cosas que necesita para hacer.

Además, si TextBoxestá en una pestaña que no es la SelectedTab, ScrollToCarat()no tendrá ningún efecto. Por lo tanto, necesita al menos convertirlo en la pestaña seleccionada mientras se desplaza. Usted puede envolver el código en una YourTab.SuspendLayout();y YourTab.ResumeLayout(false);par si el formulario parpadea cuando se hace esto.

Fin de editar

¡Espero que esto ayude!

Pete
fuente
1

Esto se desplazará hasta el final del cuadro de texto cuando se cambie el texto, pero aún permite al usuario desplazarse hacia arriba

outbox.SelectionStart = outbox.Text.Length;
outbox.ScrollToEnd();

probado en Visual Studio Enterprise 2017

Eric Shreve
fuente
1

Para cualquier otra persona que aterrice aquí y espere ver una implementación de formularios web, desea usar el controlador de eventos endRequest del Page Request Manager ( https://stackoverflow.com/a/1388170/1830512 ). Esto es lo que hice para mi TextBox en una página de contenido de una página maestra, ignore el hecho de que no utilicé una variable para el control:

var prm = Sys.WebForms.PageRequestManager.getInstance();

function EndRequestHandler() {
    if ($get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>') != null) {
        $get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>').scrollTop = 
        $get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>').scrollHeight;
    }
}

prm.add_endRequest(EndRequestHandler);
midoriha_senpai
fuente
0

Esto solo funcionó para mí ...

txtSerialLogging-> Text = "";

txtSerialLogging-> AppendText (s);

Intenté todos los casos anteriores, pero el problema es que, en mi caso, los textos pueden disminuir, aumentar y también pueden permanecer estáticos durante mucho tiempo. medios estáticos, longitud estática (líneas) pero el contenido es diferente.

Entonces, estaba enfrentando una situación de salto de línea al final cuando la longitud (líneas) permanece igual algunas veces ...

TooGeeky
fuente
Lo sé, es similar a la respuesta de Bob, pero explica un caso específico. Y no puedo comentar sobre la respuesta de Bob ... Atascado con las reglas de stackoverflow :(
TooGeeky
0

Yo uso una función para esto:

private void Log (string s) {
    TB1.AppendText(Environment.NewLine + s);
    TB1.ScrollToCaret();
}
DMike92
fuente