Como posso mostrar barras de rolagem em um System.Windows.Forms.TextBox somente quando o texto não cabe?

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Para um System.Windows.Forms.TextBox com Multiline=True, gostaria de mostrar apenas as barras de rolagem quando o texto não couber.

Esta é uma caixa de texto somente leitura usada apenas para exibição.É um TextBox para que os usuários possam copiar o texto.Existe algo integrado para suportar a exibição automática de barras de rolagem?Se não, devo usar um controle diferente?Ou preciso conectar TextChanged e verificar manualmente se há estouro (em caso afirmativo, como saber se o texto cabe?)


Não tendo sorte com várias combinações de configurações de WordWrap e barras de rolagem.Gostaria de não ter barras de rolagem inicialmente e que cada uma aparecesse dinamicamente apenas se o texto não couber na direção especificada.


@nobugz, obrigado, isso funciona quando o WordWrap está desabilitado.Eu preferiria não desativar o wordwrap, mas é o menor dos dois males.


@André Neves, bom argumento, e eu seguiria esse caminho se fosse editável pelo usuário.Concordo que a consistência é a regra fundamental para a intuitividade da IU.

Foi útil?

Solução

Adicione uma nova classe ao seu projeto e cole o código mostrado abaixo.Compilar.Solte o novo controle da parte superior da caixa de ferramentas em seu formulário.Não é totalmente perfeito, mas deve funcionar para você.

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);
  }
}

Outras dicas

Me deparei com essa pergunta quando queria resolver o mesmo problema.

A maneira mais fácil de fazer isso é mudar para System.Windows.Forms.RichTextBox.A propriedade ScrollBars neste caso pode ser deixada para o valor padrão de RichTextBoxScrollBars.both, que indica "exibir uma barra de rolagem horizontal e vertical quando necessário". Seria bom se essa funcionalidade fosse fornecida na caixa de texto.

Também fiz alguns experimentos e descobri que a barra vertical sempre será exibida se você a ativar, e a barra horizontal sempre será exibida enquanto estiver ativada e WordWrap == false.

Acho que você não vai conseguir exatamente o que deseja aqui.No entanto, acredito que os usuários gostariam de um comportamento padrão do Windows melhor do que aquele que você está tentando forçar.Se eu estivesse usando seu aplicativo, provavelmente ficaria incomodado se o espaço da minha caixa de texto diminuísse repentinamente só porque precisa acomodar uma barra de rolagem inesperada porque forneci muito texto!

Talvez fosse uma boa ideia deixar seu aplicativo seguir a aparência do Windows.

Há um bug extremamente sutil na solução do nobugz que resulta em corrupção de heap, mas apenas se você estiver usando AppendText() para atualizar o TextBox.

Definir a propriedade ScrollBars de OnTextChanged fará com que a janela Win32 (identificador) seja destruída e recriada.Mas OnTextChanged é chamado nas entranhas do controle de edição Win32 (EditML_InsertText), que imediatamente a seguir espera que o estado interno desse controle de edição Win32 permaneça inalterado.Infelizmente, desde que a janela foi recriada, esse estado interno foi liberado pelo sistema operacional, resultando em uma violação de acesso.

Então a moral da história é:não use AppendText() se for usar a solução do nobugz.

Tive algum sucesso com o código abaixo.

  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);
    }
  }

O que Aidan descreve é ​​quase exatamente o cenário de UI que estou enfrentando.Como a caixa de texto é somente leitura, não preciso dela para responder ao TextChanged.E eu preferiria que o recálculo da rolagem automática fosse atrasado para que não disparasse dezenas de vezes por segundo enquanto uma janela estivesse sendo redimensionada.

Para a maioria das UIs, caixas de texto com barras de rolagem verticais e horizontais são, bem, más, então estou interessado apenas em barras de rolagem verticais aqui.

Também descobri que MeasureString produziu uma altura que era realmente maior do que o necessário.Usar o PreferredHeight da caixa de texto sem borda como altura da linha fornece um resultado melhor.

O seguinte parece funcionar muito bem, com ou sem borda, e funciona com o WordWrap ativado.

Basta chamar AutoScrollVertical() quando precisar e, opcionalmente, especificar 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;
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top