Como rolar automaticamente para a parte inferior de uma caixa de texto com várias linhas?

295

Eu tenho uma caixa de texto com a propriedade .Multiline definida como true. A intervalos regulares, estou adicionando novas linhas de texto. Desejo que a caixa de texto role automaticamente para a entrada mais abaixo (a mais nova) sempre que uma nova linha for adicionada. Como eu faço isso?

GWLlosa
fonte
6
Procurei aqui a resposta, não consegui encontrá-la, então, quando eu descobri, achei que iria colocá-lo aqui para futuros usuários, ou se talvez alguém tivesse uma abordagem melhor.
GWLlosa
2
Eu precisava fazer a mesma coisa no VBA, que não possui todos esses novos métodos .NET sofisticados. Para o futuro google-fu, aqui está o encantamento: TextBox1.Text = TextBox1.Text & "Whatever"; TextBox1.SelStart = Len (TextBox1.Text); TextBox1.SetFocus; ... e, em seguida, um .SetFocus de volta para qualquer controle que tivesse o foco antes. Sem focar no TextBox1, ele nunca atualizaria suas barras de rolagem, não importa o que eu fiz.
Gordon Broom
1
@GordonBroom Whelp, graças a isso, vou começar a chamar "snippets de código" "encantamentos" agora. Bom trabalho. : D
Sidney

Respostas:

425

A intervalos regulares, estou adicionando novas linhas de texto. Desejo que a caixa de texto role automaticamente para a entrada mais abaixo (a mais nova) sempre que uma nova linha for adicionada.

Se você usar TextBox.AppendText(string text), ele rolará automaticamente até o final do texto recém-anexado. Evita a barra de rolagem oscilante se você estiver chamando em um loop.

Também é uma ordem de magnitude mais rápida que a concatenação na .Textpropriedade. Embora isso possa depender da frequência com que você está ligando; Eu estava testando com um loop apertado.


Isso não rolará se for chamado antes da caixa de texto ser exibida ou se a caixa de texto não estiver visível (por exemplo, em uma guia diferente de um TabPanel). Consulte TextBox.AppendText () sem rolagem automática . Isso pode ou não ser importante, dependendo se você precisa de rolagem automática quando o usuário não pode ver a caixa de texto.

Parece que o método alternativo das outras respostas também não funciona neste caso. Uma maneira de contornar isso é executar rolagem adicional no VisibleChangedevento:

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

Internamente, AppendTextfaz algo assim:

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

Mas não deve haver razão para fazê-lo manualmente.

(Se você o descompilar, verá que ele usa métodos internos possivelmente mais eficientes e possui o que parece ser um caso especial menor.)

Prumo
fonte
7
Este método é muito mais rápido e suave. Não há 'tremulação' da barra de rolagem (que é mais perceptível ao fazer muitas chamadas em sucessão rápida).
precisa saber é o seguinte
3
Esta é uma solução muito melhor.
21413 Jeff Jeff
3
Estava comendo-me a tentar fazê-lo com tb.Text += ....e WndProc e marechais Agora eu me sinto estúpido: D
Saeid Yazdani
3
A área de texto também precisa ser focada; na primeira vez que fiz isso, ela não rolou porque não tinha o foco.
Qwerty01
3
O AppendText não rolava automaticamente meu ReadOnly TextBox, mas adicionava TextBox.ScrollToEnd (); depois que a chamada AppendText fez o truque.
Brandon Barkley
143

Você pode usar o seguinte trecho de código:

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

que rolará automaticamente até o fim.

GWLlosa
fonte
5
Procurei aqui a resposta, não consegui encontrá-la, então, quando eu descobri, achei que iria colocá-lo aqui para futuros usuários, ou se talvez alguém tivesse uma abordagem melhor.
GWLlosa
4
Essa pode ter sido a melhor resposta na época, mas agora acho que a resposta de Bob é uma solução melhor para o problema do OP.
precisa saber é
38

Parece que a interface mudou no .NET 4.0. Existe o seguinte método que atinge todas as opções acima. Como sugeriu Tommy Engebretsen, colocá-lo em um manipulador de eventos TextChanged o torna automático.

textBox1.ScrollToEnd();
JohnDRoach
fonte
21
Observe que esse método está na TextBoxBaseclasse no System.Windows.Controls.Primitivesespaço para nome ( PresentationFrameworkassembly, WPF). Este método não existe e não vai funcionar no WinForms, cuja TextBoxherda classe a partir TextBoxBasedo System.Windows.Formsnamespace ( System.Windows.Formsmontagem, WinForms).
Bob
1
Observe que o ScrollToEnd()desempenho pode ser extremamente ruim. No meu aplicativo, foi responsável por mais de 50% do tempo de criação de perfil.
Ergohack 25/10
16

Tente adicionar o código sugerido ao evento TextChanged:

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

não funcionou para mim (Windows 8.1, qualquer que seja o motivo).
E como ainda estou no .NET 2.0, não posso usar o ScrollToEnd.

Mas isso 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
fonte
Teve o mesmo problema com o Windows 10, sua solução alternativa também funciona bem aqui.
Hannes
Ninguém estava trabalhando para mim, mas isso. Deus abençoe sua alma
Emirhan Özlen
9

Eu precisava adicionar uma atualização:

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

Eu encontrei uma diferença simples que não foi abordada neste tópico.

Se você estiver fazendo todas as ScrollToCarat()chamadas como parte do Load()evento do formulário , isso não funcionará. Acabei de adicionar minha ScrollToCarat()chamada ao Activated()evento do meu formulário e funciona bem.

Editar

É importante fazer essa rolagem apenas na primeira vez que o Activatedevento do formulário for disparado (não nas ativações subseqüentes), ou rolará a cada vez que seu formulário for ativado, o que provavelmente você não quer.

Portanto, se você estiver capturando apenas o Activated()evento para rolar o texto quando o programa for carregado, basta cancelar a inscrição no evento dentro do próprio manipulador de eventos, assim:

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

Se você tiver outras coisas que precisa fazer sempre que seu formulário for ativado, defina a boolcomo true na primeira vez que seu formulário for ativado .Activated() evento for disparado, para não rolar nas ativações subsequentes, mas ainda poderá fazer as outras coisas necessárias. Faz.

Além disso, se você TextBoxestiver em uma guia que não seja a SelectedTab, ScrollToCarat()não terá efeito. Portanto, você precisa pelo menos torná-la a guia selecionada enquanto estiver rolando. Você pode agrupar o código em um par YourTab.SuspendLayout();e YourTab.ResumeLayout(false);se o seu formulário piscar quando você fizer isso.

Fim da edição

Espero que isto ajude!

Pete
fonte
1

Isso rolará até o final da caixa de texto quando o texto for alterado, mas ainda permitirá que o usuário role para cima

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

testado no Visual Studio Enterprise 2017

Eric Shreve
fonte
1

Para qualquer outra pessoa que esteja aqui esperando ver uma implementação de formulários da web, deseja usar o manipulador de eventos endRequest do Page Request Manager ( https://stackoverflow.com/a/1388170/1830512 ). Aqui está o que eu fiz para o meu TextBox em uma página de conteúdo de uma página mestra, ignore o fato de que eu não usei uma variável para o controle:

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

Isso só funcionou para mim ...

txtSerialLogging-> Text = "";

txtSerialLogging-> AppendText (s);

Tentei todos os casos acima, mas o problema é que, no meu caso, os textos podem diminuir, aumentar e também podem permanecer estáticos por um longo tempo. static significa, comprimento estático (linhas), mas o conteúdo é diferente.

Então, eu estava enfrentando uma situação de salto de linha no final, quando o comprimento (linhas) permanece o mesmo por algumas vezes ...

TooGeeky
fonte
Eu sei, é semelhante à resposta de Bob, mas explica um caso específico. E eu não posso comentar a resposta de Bob ... Preso com as regras de stackoverflow :(
TooGeeky 10/10
0

Eu uso uma função para isso:

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