Wie scrolle ich automatisch zum Ende eines mehrzeiligen Textfelds?

295

Ich habe ein Textfeld mit der Eigenschaft .Multiline auf true gesetzt. In regelmäßigen Abständen füge ich neue Textzeilen hinzu. Ich möchte, dass das Textfeld automatisch zum untersten Eintrag (dem neuesten) blättert, wenn eine neue Zeile hinzugefügt wird. Wie schaffe ich das?

GWLlosa
quelle
6
Ich habe hier nach der Antwort gesucht und sie nicht gefunden. Als ich sie herausgefunden habe, habe ich mir gedacht, ich würde sie hier für zukünftige Benutzer aufstellen, oder wenn vielleicht jemand anderes einen besseren Ansatz hätte.
GWLlosa
2
Ich musste dasselbe in VBA tun, wo es nicht all diese neuen .NET-Methoden gibt. Für zukünftiges Google-Fu ist hier die Beschwörung: TextBox1.Text = TextBox1.Text & "was auch immer"; TextBox1.SelStart = Len (TextBox1.Text); TextBox1.SetFocus; ... und dann ein .SetFocus zurück zu dem Steuerelement, das zuvor den Fokus hatte. Ohne den Fokus auf TextBox1 zu legen, würde es niemals seine Bildlaufleisten aktualisieren, egal was ich getan habe.
Gordon Broom
1
@ GordonBroom Whelp, dank dessen werde ich jetzt anfangen, "Code-Snippets" "Beschwörungsformeln" zu nennen. Gute Arbeit. : D
Sidney

Antworten:

425

In regelmäßigen Abständen füge ich neue Textzeilen hinzu. Ich möchte, dass das Textfeld automatisch zum untersten Eintrag (dem neuesten) blättert, wenn eine neue Zeile hinzugefügt wird.

Wenn Sie verwenden TextBox.AppendText(string text), wird automatisch zum Ende des neu angehängten Textes gescrollt. Die flackernde Bildlaufleiste wird vermieden, wenn Sie sie in einer Schleife aufrufen.

Es ist auch eine Größenordnung schneller als die Verkettung auf dem .TextGrundstück. Dies hängt jedoch möglicherweise davon ab, wie oft Sie es anrufen. Ich habe mit einer engen Schleife getestet.


Dies wird nicht gescrollt, wenn es aufgerufen wird, bevor das Textfeld angezeigt wird, oder wenn das Textfeld ansonsten nicht sichtbar ist (z. B. auf einer anderen Registerkarte eines TabPanels). Siehe TextBox.AppendText () nicht automatisch scrollen . Dies kann wichtig sein oder auch nicht, je nachdem, ob Sie eine automatische Bildlauffunktion benötigen, wenn der Benutzer das Textfeld nicht sehen kann.

Es scheint, dass die alternative Methode aus den anderen Antworten auch in diesem Fall nicht funktioniert. Eine Möglichkeit, dies zu umgehen, besteht darin, ein zusätzliches Scrollen für das VisibleChangedEreignis durchzuführen :

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

Intern AppendTextmacht so etwas:

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

Es sollte jedoch keinen Grund geben, dies manuell zu tun.

(Wenn Sie es selbst dekompilieren, werden Sie feststellen, dass es möglicherweise effizientere interne Methoden verwendet und einen scheinbar geringfügigen Sonderfall aufweist.)

Bob
quelle
7
Diese Methode ist viel schneller und reibungsloser. Die Bildlaufleiste flackert nicht (was sich bei vielen Anrufen in schneller Folge bemerkbar macht).
TallGuy
3
Dies ist eine viel bessere Lösung.
Jeff
3
tb.Text += ....Ich habe mich selbst gegessen und versucht, es mit WndProc und Marschällen zu schaffen. Jetzt fühle ich mich dumm: D
Saeid Yazdani
3
Der Textbereich muss auch fokussiert werden. Als ich dies zum ersten Mal tat, wurde nicht gescrollt, da er nicht fokussiert war.
Qwerty01
3
AppendText hat meine ReadOnly TextBox nicht automatisch gescrollt, sondern TextBox.ScrollToEnd () hinzugefügt. Nach dem AppendText-Aufruf war der Trick erledigt.
Brandon Barkley
143

Sie können das folgende Codefragment verwenden:

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

Das wird automatisch bis zum Ende scrollen.

GWLlosa
quelle
5
Ich habe hier nach der Antwort gesucht und sie nicht gefunden. Als ich sie herausgefunden habe, habe ich mir gedacht, ich würde sie hier für zukünftige Benutzer aufstellen, oder wenn vielleicht jemand anderes einen besseren Ansatz hätte.
GWLlosa
4
Dies war damals vielleicht die beste Antwort, aber jetzt denke ich, dass Bobs Antwort eine bessere Lösung für das Problem des OP ist.
Tomsv
38

Es scheint, dass sich die Schnittstelle in .NET 4.0 geändert hat . Es gibt die folgende Methode , mit der alle oben genannten Ziele erreicht werden. Wie Tommy Engebretsen vorgeschlagen hat, wird es automatisch in einen TextChanged-Ereignishandler eingefügt.

textBox1.ScrollToEnd();
JohnDRoach
quelle
21
Beachten Sie, dass sich diese Methode in der TextBoxBaseKlasse im System.Windows.Controls.PrimitivesNamespace befindet ( PresentationFrameworkAssembly, WPF). Diese Methode gibt es nicht und wird nicht funktionieren in WinForms, deren TextBoxKlasse erbt von TextBoxBasedem System.Windows.FormsNamensraum ( System.Windows.FormsMontag, WinForms).
Bob
1
Beachten Sie, dass ScrollToEnd()die Leistung extrem schlecht sein kann. In meiner App machte es über 50% der Profilierungszeit aus.
Ergohack
16

Versuchen Sie, den vorgeschlagenen Code zum TextChanged-Ereignis hinzuzufügen:

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

hat bei mir nicht funktioniert (Windows 8.1, aus welchem ​​Grund auch immer).
Und da ich immer noch auf .NET 2.0 bin, kann ich ScrollToEnd nicht verwenden.

Aber das funktioniert:

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
quelle
Hatte das gleiche Problem mit Windows 10, funktioniert Ihre Problemumgehung auch hier einwandfrei.
Hannes
Keiner hat für mich gearbeitet, aber das hier. Gott segne deine Seele
Emirhan Özlen
9

Ich musste eine Aktualisierung hinzufügen:

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

Ich habe einen einfachen Unterschied gefunden, der in diesem Thread nicht angesprochen wurde.

Wenn Sie alle ScrollToCarat()Anrufe im Rahmen des Load()Ereignisses Ihres Formulars ausführen, funktioniert dies nicht. Ich habe gerade meinen ScrollToCarat()Anruf zum Activated()Ereignis meines Formulars hinzugefügt , und es funktioniert einwandfrei.

Bearbeiten

Es ist wichtig, dass Sie diesen Bildlauf nur beim ersten ActivatedAuslösen des Formularereignisses durchführen (nicht bei nachfolgenden Aktivierungen). Andernfalls wird jedes Mal gescrollt Mal wenn Ihr Formular aktiviert wird. ist wahrscheinlich etwas, das Sie nicht möchten.

Wenn Sie also das Activated()Ereignis nur einfangen , um beim Laden Ihres Programms durch Ihren Text zu scrollen, können Sie das Ereignis im Ereignishandler selbst einfach abbestellen.

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

Wenn Sie bei jeder Aktivierung Ihres Formulars andere Aufgaben ausführen müssen, können Sie a boolbeim ersten Mal auf true setzenActivated() Auslösen Ereignisses , damit Sie bei nachfolgenden Aktivierungen nicht scrollen, aber dennoch die anderen erforderlichen Aufgaben ausführen können machen.

Wenn Sie TextBoxsich auf einer Registerkarte befinden, die nicht die ist SelectedTab, ScrollToCarat()hat dies keine Auswirkung. Sie müssen es also mindestens zur ausgewählten Registerkarte machen, während Sie scrollen. Sie können den Code in ein YourTab.SuspendLayout();und ein YourTab.ResumeLayout(false);Paar einschließen, wenn Ihr Formular dabei flackert.

Ende der Bearbeitung

Hoffe das hilft!

Pete
quelle
1

Dadurch wird beim Ändern des Texts bis zum Ende des Textfelds gescrollt, der Benutzer kann jedoch weiterhin nach oben scrollen

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

getestet auf Visual Studio Enterprise 2017

Eric Shreve
quelle
1

Für alle anderen, die hier landen und eine Webforms-Implementierung erwarten, möchten Sie den endRequest-Ereignishandler des Page Request Managers ( https://stackoverflow.com/a/1388170/1830512 ) verwenden. Folgendes habe ich für meine TextBox auf einer Inhaltsseite von einer Masterseite aus getan. Bitte ignorieren Sie die Tatsache, dass ich keine Variable für das Steuerelement verwendet habe:

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
quelle
0

Das hat nur bei mir funktioniert ...

txtSerialLogging-> Text = "";

txtSerialLogging-> AppendText (s);

Ich habe alle oben genannten Fälle ausprobiert, aber das Problem ist, dass in meinem Fall die Texte abnehmen, zunehmen und auch lange statisch bleiben können. statisch bedeutet, statische Länge (Linien), aber Inhalt ist unterschiedlich.

Also war ich am Ende mit einer Liniensprung-Situation konfrontiert, als die Länge (Linien) für einige Male gleich blieb ...

TooGeeky
quelle
Ich weiß, es ähnelt Bobs Antwort, erklärt aber einen bestimmten Fall. UND ich kann Bobs Antwort nicht kommentieren ... Ich habe mich an die Stackoverflow-Regeln gehalten :(
TooGeeky
0

Ich benutze dafür eine Funktion:

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