Comment puis-je afficher des barres de défilement sur un System.Windows.Forms.TextBox uniquement lorsque le texte ne tient pas?

StackOverflow https://stackoverflow.com/questions/73110

  •  09-06-2019
  •  | 
  •  

Question

Pour un System.Windows.Forms.TextBox avec Multiline = True, je voudrais afficher uniquement les barres de défilement lorsque le texte ne correspond pas.

Ceci est une zone de texte en lecture seule utilisée uniquement pour l'affichage. C'est une zone de texte pour que les utilisateurs puissent copier le texte. Y a-t-il quelque chose d'intégré pour supporter l'affichage automatique des barres de défilement? Si non, devrais-je utiliser un contrôle différent? Ou dois-je accrocher TextChanged et vérifier manuellement le débordement (si oui, comment savoir si le texte convient?)

Ne pas avoir de chance avec différentes combinaisons de paramètres WordWrap et Scrollbars. Au départ, j'aimerais ne pas avoir de barres de défilement et les afficher de manière dynamique uniquement si le texte ne correspond pas à la direction indiquée.

@nobugz, merci, cela fonctionne lorsque WordWrap est désactivé. Je préférerais ne pas désactiver Wordwrap, mais c'est le moindre des deux maux.

@ Andr & # 233; Neves, bon point, et j'irais dans ce sens si c'était modifiable par l'utilisateur. Je conviens que la cohérence est la règle fondamentale de l'intuitivité de l'interface utilisateur.

Était-ce utile?

La solution

Ajoutez une nouvelle classe à votre projet et collez le code ci-dessous. Compiler. Déposez le nouveau contrôle du haut de la boîte à outils sur votre formulaire. Ce n'est pas tout à fait parfait, mais cela devrait fonctionner pour vous.

using System;
using System.Drawing;
using System.Windows.Forms;

public class MyTextBox : TextBox {
  private bool mScrollbars;
  public MyTextBox() {
    this.Multiline = true;
    this.ReadOnly = true;
  }
  private void checkForScrollbars() {
    bool scroll = false;
    int cnt = this.Lines.Length;
    if (cnt > 1) {
      int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
      if (pos0 >= 32768) pos0 -= 65536;
      int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
      if (pos1 >= 32768) pos1 -= 65536;
      int h = pos1 - pos0;
      scroll = cnt * h > (this.ClientSize.Height - 6);  // 6 = padding
    }
    if (scroll != mScrollbars) {
      mScrollbars = scroll;
      this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
    }
  }

  protected override void OnTextChanged(EventArgs e) {
    checkForScrollbars();
    base.OnTextChanged(e);
  }

  protected override void OnClientSizeChanged(EventArgs e) {
    checkForScrollbars();
    base.OnClientSizeChanged(e);
  }
}

Autres conseils

Je suis tombé sur cette question lorsque je voulais résoudre le même problème.

La méthode la plus simple consiste à passer à System.Windows.Forms.RichTextBox. Dans ce cas, la propriété ScrollBars peut être laissée à la valeur par défaut de RichTextBoxScrollBars.Both, qui indique "Afficher une barre de défilement horizontale et une barre verticale en cas de besoin." Ce serait bien si cette fonctionnalité était fournie sur TextBox.

J'ai également fait quelques expériences et constaté que la barre verticale s'affichera toujours si vous l'activez, et que la barre horizontale indique toujours tant qu'elle est activée et que WordWrap == false .

Je pense que vous n'allez pas obtenir exactement ce que vous voulez ici. Cependant, je pense que les utilisateurs souhaiteraient un meilleur comportement par défaut de Windows que celui que vous essayez de forcer. Si j'utilisais votre application, je serais probablement dérangé par le rétrécissement subtil de mon espace texte simplement parce qu'il doit accueillir une barre de défilement inattendue, car je lui ai donné trop de texte!

Ce serait peut-être une bonne idée de laisser votre application suivre l'apparence de Windows.

Il y a un bogue extrêmement subtil dans la solution de nobugz qui entraîne une corruption de tas, mais uniquement si vous utilisez AppendText () pour mettre à jour la zone de texte.

La définition de la propriété ScrollBars à partir de OnTextChanged entraînera la destruction de la fenêtre Win32 (descripteur) et sa recréation. Mais OnTextChanged est appelé à partir des entrailles du contrôle d'édition Win32 (EditML_InsertText), qui s'attend ensuite à ce que l'état interne de ce contrôle d'édition Win32 reste inchangé. Malheureusement, depuis la recréation de la fenêtre, cet état interne a été libéré par le système d'exploitation, ce qui a entraîné une violation d'accès.

La morale de l'histoire est donc la suivante: n'utilisez pas AppendText () si vous utilisez la solution de nobugz.

J'ai eu du succès avec le code ci-dessous.

  public partial class MyTextBox : TextBox
  {
    private bool mShowScrollBar = false;

    public MyTextBox()
    {
      InitializeComponent();

      checkForScrollbars();
    }

    private void checkForScrollbars()
    {
      bool showScrollBar = false;
      int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;

      using (Graphics g = this.CreateGraphics())
      {
        // Calcualte the size of the text area.
        SizeF textArea = g.MeasureString(this.Text,
                                         this.Font,
                                         this.Bounds.Width - padding);

        if (this.Text.EndsWith(Environment.NewLine))
        {
          // Include the height of a trailing new line in the height calculation        
          textArea.Height += g.MeasureString("A", this.Font).Height;
        }

        // Show the vertical ScrollBar if the text area
        // is taller than the control.
        showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));

        if (showScrollBar != mShowScrollBar)
        {
          mShowScrollBar = showScrollBar;
          this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
        }
      }
    }

    protected override void OnTextChanged(EventArgs e)
    {
      checkForScrollbars();
      base.OnTextChanged(e);
    }

    protected override void OnResize(EventArgs e)
    {
      checkForScrollbars();
      base.OnResize(e);
    }
  }

Ce que Aidan décrit correspond presque exactement au scénario d’interface utilisateur auquel je suis confronté. Comme la zone de texte est en lecture seule, je n’en ai pas besoin pour répondre à TextChanged. Et je préférerais que le recalcul automatique du défilement soit différé afin de ne pas déclencher des dizaines de fois par seconde pendant le redimensionnement d'une fenêtre.

Pour la plupart des interfaces utilisateur, les zones de texte comportant à la fois des barres de défilement verticale et horizontale sont diaboliques. Je ne m'intéresse donc qu'aux barres de défilement verticales ici.

J'ai également constaté que MeasureString produisait une hauteur plus grande que celle requise. L’utilisation de PreferredHeight dans la zone de texte sans bordure car la hauteur de ligne donne un meilleur résultat.

Ce qui suit semble fonctionner plutôt bien, avec ou sans bordure, et fonctionne avec WordWrap.

Appelez simplement AutoScrollVertically () lorsque vous en avez besoin et spécifiez éventuellement recalculateOnResize.

public class TextBoxAutoScroll : TextBox
{
    public void AutoScrollVertically(bool recalculateOnResize = false)
    {
        SuspendLayout();

        if (recalculateOnResize)
        {
            Resize -= OnResize;
            Resize += OnResize;
        }

        float linesHeight = 0;
        var   borderStyle = BorderStyle;

        BorderStyle       = BorderStyle.None;

        int textHeight    = PreferredHeight;

        try
        {
            using (var graphics = CreateGraphics())
            {
                foreach (var text in Lines)
                {
                    var textArea = graphics.MeasureString(text, Font);

                    if (textArea.Width < Width)
                        linesHeight += textHeight;
                    else
                    {
                        var numLines = (float)Math.Ceiling(textArea.Width / Width);

                        linesHeight += textHeight * numLines;
                    }
                }
            }

            if (linesHeight > Height)
                ScrollBars = ScrollBars.Vertical;
            else
                ScrollBars = ScrollBars.None;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
        }
        finally
        {
            BorderStyle = borderStyle;

            ResumeLayout();
        }
    }

    private void OnResize(object sender, EventArgs e)
    {
        m_timerResize.Stop();

        m_timerResize.Tick    -= OnDelayedResize;
        m_timerResize.Tick    += OnDelayedResize;
        m_timerResize.Interval = 475;

        m_timerResize.Start();
    }

    Timer m_timerResize = new Timer();

    private void OnDelayedResize(object sender, EventArgs e)
    {
        m_timerResize.Stop();

        Resize -= OnResize;

        AutoScrollVertically();

        Resize += OnResize;
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top