Jak automatycznie przewinąć do dołu wielowierszowego pola tekstowego?

295

Mam pole tekstowe z właściwością .Multiline ustawioną na true. W regularnych odstępach czasu dodam do niego nowe wiersze tekstu. Chciałbym, aby pole tekstowe automatycznie przewijało się do najniższego wpisu (najnowszego) za każdym razem, gdy dodawany jest nowy wiersz. Jak to osiągnąć?

GWLlosa
źródło
6
Poszukałem tutaj odpowiedzi, nie mogłem jej znaleźć, więc kiedy ją rozgryzłem, pomyślałem, że postawię ją tutaj dla przyszłych użytkowników, a może ktoś inny miałby lepsze podejście.
GWLlosa
2
Musiałem zrobić to samo w VBA, który nie ma tych wszystkich wymyślnych nowych metod .NET. W przypadku przyszłego google-fu, oto inkantacja: TextBox1.Text = TextBox1.Text i „cokolwiek”; TextBox1.SelStart = Len (TextBox1.Text); TextBox1.SetFocus; ... a następnie .SetFocus z powrotem do jakiejkolwiek kontroli, na której skupiał się wcześniej. Bez skupienia się na TextBox1, nigdy nie aktualizowałby swoich pasków przewijania, bez względu na to, co zrobiłem.
Gordon Broom
1
@GordonBroom Whelp, dzięki czemu zacznę teraz nazywać „fragmenty kodu” „inkantacjami”. Dobra robota. : D
Sidney,

Odpowiedzi:

425

W regularnych odstępach czasu dodam do niego nowe wiersze tekstu. Chciałbym, aby pole tekstowe automatycznie przewijało się do najniższego wpisu (najnowszego) za każdym razem, gdy dodawany jest nowy wiersz.

Jeśli go użyjesz TextBox.AppendText(string text), nastąpi automatyczne przewinięcie do końca nowo dołączonego tekstu. Pozwala to uniknąć migotania paska przewijania, jeśli wywołujesz go w pętli.

Zdarza się również, że jest o rząd wielkości szybszy niż konkatenacja na .Textnieruchomości. Chociaż może to zależeć od tego, jak często go nazywasz; Testowałem z ciasną pętlą.


Nie przewinie się, jeśli zostanie wywołane przed wyświetleniem pola tekstowego lub jeśli pole tekstowe nie będzie widoczne (np. W innej zakładce TabPanel). Zobacz TextBox.AppendText () nie przewijający się automatycznie . To może, ale nie musi być ważne, w zależności od tego, czy potrzebujesz automatycznego przewijania, gdy użytkownik nie widzi pola tekstowego.

Wydaje się, że alternatywna metoda z innych odpowiedzi również nie działa w tym przypadku. Jednym sposobem jest dodatkowe przewijanie VisibleChangedzdarzenia:

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

Wewnętrznie AppendTextrobi coś takiego:

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

Ale nie powinno być żadnego powodu, aby robić to ręcznie.

(Jeśli sam go zdekompilujesz, zobaczysz, że używa on prawdopodobnie bardziej wydajnych metod wewnętrznych i ma coś, co wydaje się być drobnym przypadkiem szczególnym.)

Kok
źródło
7
Ta metoda jest znacznie szybsza i płynniejsza. Nie ma „migotania” paska przewijania (co jest bardziej zauważalne przy szybkim nawiązywaniu wielu połączeń).
TallGuy 11.04.13
3
To jest znacznie lepsze rozwiązanie.
Jeff
3
tb.Text += ....
Jadłem
3
Obszar tekstowy również musi być skoncentrowany, za pierwszym razem, gdy to robiłem, nie przewijał się, ponieważ nie miał fokusa.
Qwerty01
3
AppendText nie przewijał automatycznie mojego ReadOnly TextBox, ale dodał TextBox.ScrollToEnd (); po wywołaniu AppendText załatwiło sprawę.
Brandon Barkley,
143

Możesz użyć następującego fragmentu kodu:

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

który automatycznie przewinie do końca.

GWLlosa
źródło
5
Poszukałem tutaj odpowiedzi, nie mogłem jej znaleźć, więc kiedy ją rozgryzłem, pomyślałem, że postawię ją tutaj dla przyszłych użytkowników, a może ktoś inny miałby lepsze podejście.
GWLlosa
4
Być może była to najlepsza odpowiedź w tamtym czasie, ale teraz myślę, że odpowiedź Boba jest lepszym rozwiązaniem problemu PO.
tomsv
38

Wygląda na to, że interfejs zmienił się w .NET 4.0. Istnieje następująca metoda, która pozwala spełnić wszystkie powyższe kryteria. Jak sugerował Tommy Engebretsen, umieszczenie go w module obsługi zdarzeń TextChanged sprawia, że ​​jest on automatyczny.

textBox1.ScrollToEnd();
JohnDRoach
źródło
21
Zauważ, że ta metoda znajduje się w TextBoxBaseklasie w System.Windows.Controls.Primitivesprzestrzeni nazw ( PresentationFrameworkasembler, WPF). Ta metoda nie istnieje i nie będzie działać w WinForms, których TextBoxklasa dziedziczy TextBoxBasepo System.Windows.Formsprzestrzeni nazw ( System.Windows.Formsasembler, WinForms).
Bob
1
Zauważ, że ScrollToEnd()może być bardzo słaba wydajność. W mojej aplikacji stanowiło to ponad 50% czasu profilowania.
ergohack
16

Spróbuj dodać sugerowany kod do zdarzenia TextChanged:

private void textBox1_TextChanged(object sender, EventArgs e)
{
  textBox1.SelectionStart = textBox1.Text.Length;
  textBox1.ScrollToCaret();
}
GWLlosa
źródło
10
textBox1.Focus()
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();

nie działało dla mnie (Windows 8.1, bez względu na przyczynę).
A ponieważ wciąż korzystam z platformy .NET 2.0, nie mogę używać ScrollToEnd.

Ale to działa:

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
źródło
Miał ten sam problem z systemem Windows 10, tutaj również można obejść to obejście.
Hannes
Nikt nie pracował dla mnie, ale to. Niech Bóg błogosławi twoją duszę
Emirhan Özlen
9

Musiałem dodać odświeżenie:

textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox1.Refresh();
h4nd
źródło
4

Znalazłem prostą różnicę, której nie uwzględniono w tym wątku.

Jeśli wykonujesz wszystkie ScrollToCarat()połączenia w ramach Load()zdarzenia formularza , nie działa. Właśnie dodałem swoje ScrollToCarat()wezwanie do Activated()wydarzenia w moim formularzu i działa dobrze.

Edytować

Ważne jest, aby przewijać to tylko przy pierwszym uruchomieniu Activatedzdarzenia formularza (nie przy kolejnych aktywacjach), lub będzie ono przewijać się przy każdej aktywacji formularza, czego prawdopodobnie nie chcesz.

Jeśli więc łapiesz tylko Activated()zdarzenie, aby przewinąć tekst podczas ładowania programu, możesz po prostu anulować subskrypcję zdarzenia w samej procedurze obsługi zdarzeń, a zatem:

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

Jeśli masz inne rzeczy do zrobienia za każdym razem, gdy formularz jest aktywowany, możesz ustawić wartość booltrue za pierwszym razemActivated() zdarzenia, aby nie przewijać kolejnych aktywacji, ale nadal możesz robić inne rzeczy, które musisz zrobić robić.

Ponadto, jeśli TextBoxjest na karcie, że nie jest SelectedTab, ScrollToCarat()nie będzie miało wpływu. Musisz więc przynajmniej zaznaczyć tę zakładkę podczas przewijania. Możesz owinąć kod w parę YourTab.SuspendLayout();i YourTab.ResumeLayout(false);sparować, jeśli Twój formularz migocze.

Koniec edycji

Mam nadzieję że to pomoże!

Pete
źródło
1

Spowoduje to przewinięcie do końca pola tekstowego, gdy tekst zostanie zmieniony, ale nadal pozwoli użytkownikowi przewinąć w górę

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

przetestowany na Visual Studio Enterprise 2017

Eric Shreve
źródło
1

Dla każdego, kto ląduje tutaj, oczekując implementacji formularzy internetowych, należy skorzystać z modułu obsługi zdarzeń endRequest Menedżera żądań strony ( https://stackoverflow.com/a/1388170/1830512 ). Oto, co zrobiłem dla TextBoxa na stronie treści ze strony wzorcowej, proszę zignorować fakt, że nie użyłem zmiennej do kontroli:

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
źródło
0

To działało tylko dla mnie ...

txtSerialLogging-> Text = "";

txtSerialLogging-> AppendText (s);

Próbowałem wszystkich powyższych przypadków, ale problem polega na tym, że w moim przypadku tekst może się zmniejszać, zwiększać, a także może pozostawać statyczny przez długi czas. środki statyczne, długość statyczna (linie), ale treść jest inna.

Tak więc pod koniec miałem do czynienia z sytuacją przeskakiwania jednej linii, gdy długość (linie) przez pewien czas pozostawała taka sama ...

TooGeeky
źródło
Wiem, jest podobny do odpowiedzi Boba, ale wyjaśnia konkretny przypadek. I nie mogę skomentować odpowiedzi Boba ...
Utknąłem
0

Używam do tego funkcji:

private void Log (string s) {
    TB1.AppendText(Environment.NewLine + s);
    TB1.ScrollToCaret();
}
DMike92
źródło