Comment faire défiler automatiquement vers le bas d'une zone de texte multiligne?

295

J'ai une zone de texte avec la propriété .Multiline définie sur true. À intervalles réguliers, j'y ajoute de nouvelles lignes de texte. J'aimerais que la zone de texte défile automatiquement jusqu'à l'entrée la plus basse (la plus récente) chaque fois qu'une nouvelle ligne est ajoutée. Comment est-ce que j'accomplis ceci?

GWLlosa
la source
6
J'ai cherché ici la réponse, je n'ai pas pu la trouver, alors quand je l'ai trouvée, j'ai pensé que je la mettrais ici pour les futurs utilisateurs, ou si quelqu'un d'autre avait une meilleure approche.
GWLlosa
2
Je devais faire la même chose dans VBA, qui n'a pas toutes ces nouvelles méthodes .NET fantaisistes. Pour le futur google-fu, voici l'incantation: TextBox1.Text = TextBox1.Text & "que ce soit"; TextBox1.SelStart = Len (TextBox1.Text); TextBox1.SetFocus; ... puis un .SetFocus sur le contrôle qui avait le focus auparavant. Sans donner la priorité à TextBox1, il ne mettrait jamais à jour ses barres de défilement, quoi que je fasse.
Gordon Broom
1
@GordonBroom Whelp, grâce à cela, je vais commencer à appeler des "extraits de code" "incantations" maintenant. Bon travail. : D
Sidney

Réponses:

425

À intervalles réguliers, j'y ajoute de nouvelles lignes de texte. J'aimerais que la zone de texte défile automatiquement jusqu'à l'entrée la plus basse (la plus récente) chaque fois qu'une nouvelle ligne est ajoutée.

Si vous utilisez TextBox.AppendText(string text), il défilera automatiquement jusqu'à la fin du texte nouvellement ajouté. Cela évite la barre de défilement vacillante si vous l'appelez en boucle.

Il se trouve également que c'est un ordre de grandeur plus rapide que la concaténation sur la .Textpropriété. Bien que cela puisse dépendre de la fréquence à laquelle vous l'appelez; Je testais avec une boucle serrée.


Cela ne défilera pas s'il est appelé avant que la zone de texte ne soit affichée, ou si la zone de texte n'est pas visible autrement (par exemple dans un onglet différent d'un TabPanel). Voir TextBox.AppendText () pas de défilement automatique . Cela peut être important ou non, selon que vous avez besoin du défilement automatique lorsque l'utilisateur ne peut pas voir la zone de texte.

Il semble que la méthode alternative des autres réponses ne fonctionne pas non plus dans ce cas. Une façon de le contourner consiste à effectuer un défilement supplémentaire sur l' VisibleChangedévénement:

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

En interne, AppendTextfait quelque chose comme ceci:

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

Mais il ne devrait y avoir aucune raison de le faire manuellement.

(Si vous le décompilez vous-même, vous verrez qu'il utilise des méthodes internes éventuellement plus efficaces et a ce qui semble être un cas spécial mineur.)

Bob
la source
7
Cette méthode est beaucoup plus rapide et plus fluide. Il n'y a pas de «scintillement» de la barre de défilement (ce qui est plus visible lorsque vous effectuez de nombreux appels en succession rapide).
TallGuy
3
C'est une bien meilleure solution.
Jeff
3
tb.Text += ....Je me mangeais en essayant de le faire avec WndProc et les maréchaux Maintenant, je me sens stupide: D
Saeid Yazdani
3
La zone de texte doit également être focalisée, la première fois que je l'ai fait, elle n'a pas défilé car elle n'avait pas le focus.
Qwerty01
3
AppendText n'a pas fait défiler automatiquement mon ReadOnly TextBox, mais a ajouté TextBox.ScrollToEnd (); après l'appel AppendText a fait l'affaire.
Brandon Barkley
143

Vous pouvez utiliser l'extrait de code suivant:

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

qui défilera automatiquement jusqu'à la fin.

GWLlosa
la source
5
J'ai cherché ici la réponse, je n'ai pas pu la trouver, alors quand je l'ai trouvée, j'ai pensé que je la mettrais ici pour les futurs utilisateurs, ou si quelqu'un d'autre avait une meilleure approche.
GWLlosa
4
C'était peut-être la meilleure réponse à l'époque, mais maintenant je pense que la réponse de Bob est une meilleure solution au problème du PO.
tomsv
38

Il semble que l'interface ait changé dans .NET 4.0. Il existe la méthode suivante qui réalise tout ce qui précède. Comme l'a suggéré Tommy Engebretsen, le placer dans un gestionnaire d'événements TextChanged le rend automatique.

textBox1.ScrollToEnd();
JohnDRoach
la source
21
Notez que cette méthode se trouve dans la TextBoxBaseclasse de l' System.Windows.Controls.Primitivesespace de noms ( PresentationFrameworkassembly, WPF). Cette méthode n'existe pas et ne fonctionnera pas dans WinForms, dont la TextBoxclasse hérite de TextBoxBasel' System.Windows.Formsespace de noms ( System.Windows.Formsassembly, WinForms).
Bob
1
Notez que cela ScrollToEnd()peut être extrêmement médiocre. Dans mon application, cela représentait plus de 50% du temps de profilage.
ergohack
16

Essayez d'ajouter le code suggéré à l'événement TextChanged:

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

n'a pas fonctionné pour moi (Windows 8.1, quelle qu'en soit la raison).
Et comme je suis toujours sur .NET 2.0, je ne peux pas utiliser ScrollToEnd.

Mais cela fonctionne:

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
la source
Eu le même problème avec Windows 10, votre solution de contournement fonctionne bien ici aussi.
Hannes
Personne ne travaillait pour moi, mais ça. Que Dieu vous bénisse
Emirhan Özlen
9

J'avais besoin d'ajouter un rafraîchissement:

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

J'ai trouvé une différence simple qui n'a pas été abordée dans ce fil.

Si vous effectuez tous les ScrollToCarat()appels dans le cadre de l' Load()événement de votre formulaire , cela ne fonctionne pas. Je viens d'ajouter mon ScrollToCarat()appel à l' Activated()événement de mon formulaire , et cela fonctionne bien.

Éditer

Il est important de faire ce défilement uniquement lorsque l' Activatedévénement du premier formulaire est déclenché (pas lors des activations suivantes), ou il défilera chaque fois que votre formulaire sera activé, ce que vous ne voulez probablement pas.

Donc, si vous ne faites que piéger l' Activated()événement pour faire défiler votre texte lors du chargement de votre programme, vous pouvez simplement vous désabonner de l'événement dans le gestionnaire d'événements lui-même, donc:

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

Si vous avez d'autres choses à faire chaque fois que votre formulaire est activé, vous pouvez définir a boolsur true la première fois que votre Activated()événement est déclenché, de sorte que vous ne faites pas défiler les activations suivantes, mais vous pouvez toujours faire les autres choses dont vous avez besoin pour faire.

De plus, si votre TextBoxest sur un onglet qui n'est pas le SelectedTab, ScrollToCarat()n'aura aucun effet. Vous devez donc au moins en faire l'onglet sélectionné lorsque vous faites défiler. Vous pouvez encapsuler le code dans une paire YourTab.SuspendLayout();et YourTab.ResumeLayout(false);si votre formulaire scintille lorsque vous effectuez cette opération.

Fin du montage

J'espère que cela t'aides!

Pete
la source
1

Cela défile jusqu'à la fin de la zone de texte lorsque le texte est modifié, mais permet toujours à l'utilisateur de faire défiler vers le haut

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

testé sur Visual Studio Enterprise 2017

Eric Shreve
la source
1

Pour quiconque atterrissant ici et s'attendant à voir une implémentation de formulaires Web, vous souhaitez utiliser le gestionnaire d'événements endRequest du Page Request Manager ( https://stackoverflow.com/a/1388170/1830512 ). Voici ce que j'ai fait pour mon TextBox dans une page de contenu à partir d'une page maître, veuillez ignorer le fait que je n'ai pas utilisé de variable pour le contrôle:

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
la source
0

Cela n'a fonctionné que pour moi ...

txtSerialLogging-> Text = "";

txtSerialLogging-> AppendText (s);

J'ai essayé tous les cas ci-dessus, mais le problème est que, dans mon cas, le texte peut diminuer, augmenter et peut également rester statique pendant longtemps. statique signifie, longueur statique (lignes) mais le contenu est différent.

Donc, je faisais face à une situation de saut de ligne à la fin lorsque la longueur (lignes) reste la même pendant quelques temps ...

TooGeeky
la source
Je sais, c'est similaire à la réponse de Bob, mais explique un cas spécifique. ET je ne peux pas commenter la réponse de Bob ... Coincé avec les règles de stackoverflow :(
TooGeeky
0

J'utilise une fonction pour cela:

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