web-dev-qa-db-fra.com

Comment faire défiler automatiquement jusqu'au bas d'une zone de texte multiligne?

J'ai une zone de texte avec la propriété .Multiline définie sur true. J'y ajoute de nouvelles lignes de texte à intervalles réguliers. Je voudrais que la zone de texte défile automatiquement vers l'entrée la plus basse (la plus récente) à chaque fois qu'une nouvelle ligne est ajoutée. Comment puis-je accomplir cela?

271
GWLlosa

J'y ajoute de nouvelles lignes de texte à intervalles réguliers. Je voudrais que la zone de texte défile automatiquement vers 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) , le texte défilera automatiquement à la fin du texte ajouté. Cela évite la barre de défilement clignotante si vous l’appelez en boucle.

Il se trouve également que l'ordre de grandeur est plus rapide que la concaténation sur la propriété .Text. Bien que cela dépende 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 l'affichage de la zone de texte, ou si la zone de texte n'est pas visible (par exemple, dans un onglet différent d'un TabPanel). Voir TextBox.AppendText () not autoscrolling . Cela peut être important ou non, selon que vous ayez 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 solution consiste à faire défiler davantage l'événement VisibleChanged:

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

En interne, AppendText fait 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 potentiellement plus efficaces et que ce qui semble être un cas spécial mineur.)

388
Bob

Vous pouvez utiliser l'extrait de code suivant:

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

qui défilera automatiquement à la fin.

141
GWLlosa

Il semble que l'interface ait changé dans . NET 4.0. Il existe ce qui suit méthode qui permet de réaliser 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();
39
JohnDRoach

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();
}
15
Tommy Engebretsen

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

textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox1.Refresh();
8
h4nd
textBox1.Focus()
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();

ne fonctionnait pas pour moi (Windows 8.1, quelle que 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
8
Stefan Steiger

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

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

Éditer

Il est important de ne faire que faire défiler l'événement Activated du premier formulaire est déclenché (et non lors d'activations ultérieures). Sinon, il défilera tous les votre formulaire est activé. tu veux pas.

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

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

Si vous avez d'autres tâches à faire chaque fois que votre formulaire est activé, vous pouvez définir un bool sur true la première fois que votre événement Activated() est déclenché. Vous ne pouvez donc pas faire défiler les activations suivantes. mais peut toujours faire les autres choses que vous devez faire.

De plus, si votre TextBox est 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é pendant le défilement. Vous pouvez envelopper le code dans une paire YourTab.SuspendLayout(); et YourTab.ResumeLayout(false); si votre formulaire scintille lorsque vous procédez ainsi.

Fin de l'édition

J'espère que cela t'aides!

3
Pete

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

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

testé sur Visual Studio Enterprise 2017

1
Eric Shreve

Pour quiconque atterrissant ici 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 ma zone de texte 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);
1
G.P. Greenleaf

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 une longue période. 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 ...

0
TooGeeky

J'utilise une fonction pour cela:

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