Barra de rolagem vertical automática no WPF TextBlock?

335

Eu tenho um TextBlockno WPF. Escrevo muitas linhas, excedendo em muito sua altura vertical. Eu esperava que uma barra de rolagem vertical aparecesse automaticamente quando isso acontecesse, mas não apareceu. Tentei procurar uma propriedade da barra de rolagem no painel Propriedades, mas não consegui encontrar uma.

Como posso fazer a barra de rolagem vertical criada automaticamente para TextBlockuma vez que seu conteúdo excede sua altura?

Esclarecimento: Prefiro fazê-lo do designer e não escrevendo diretamente no XAML.

Bab Yogoo
fonte
11
Ao reler esta pergunta, notei que você mencionou TextBlockduas e TextBoxuma vez.
precisa

Respostas:

554

Embrulhe-o em um visualizador de rolagem:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

NOTA: esta resposta se aplica a um TextBlock(um elemento de texto somente leitura), conforme solicitado na pergunta original.

Se você deseja mostrar as barras de rolagem em um TextBox(um elemento de texto editável), use as ScrollViewerpropriedades anexadas:

<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ScrollViewer.VerticalScrollBarVisibility="Auto" />

Os valores válidos para estas duas propriedades são Disabled, Auto, Hiddene Visible.

Drew Noakes
fonte
2
Como faço isso do designer?
29829 Bab Yogoo
16
Desculpe, não tenho certeza, não uso o designer do WPF. Acho que se você adicionar o XAML diretamente, o designer se atualizará.
de Drew Noakes
5
@conqenator TextBox.ScrollToEnd ();
precisa
2
@ Greg, a questão é sobre TextBlocknão TextBox.
de Drew Noakes
7
Às vezes, é necessário um MaxHeight no Scrollviewer para forçar a exibição do scoll se o elemento envolvente não impõe nenhuma altura.
HackerBaloo
106

pode usar o seguinte agora:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>
vince
fonte
19
@jjnguy, interpretei a pergunta original como TextBlocknão sendo TextBox(como no título e na linha de abertura), mas no segundo parágrafo mencionado TextBox. Para ser claro, esta resposta é definitivamente a melhor abordagem para texto caixas , e meu é o melhor que eu conheço para texto blocos :)
de Drew Noakes
@ Drew, ah, faz sentido. Obrigado pelo esclarecimento.
jjnguy
2
Funcionou melhor para mim também. Para um TextBox, pelo menos, ao usar o ScrollViewer ao seu redor, como na resposta aceita, as bordas do TextBox desaparecem, porque todo o controle é rolado e não apenas seu conteúdo.
Alimentada
20

Algo melhor seria:

<Grid Width="Your-specified-value" >
    <ScrollViewer>
         <TextBlock Width="Auto" TextWrapping="Wrap" />
    </ScrollViewer>
</Grid>

Isso garante que o texto no seu bloco de texto não ultrapasse e sobreponha os elementos abaixo do bloco de texto, como pode ser o caso se você não usar a grade. Isso aconteceu comigo quando tentei outras soluções, embora o bloco de texto já estivesse em uma grade com outros elementos. Lembre-se de que a largura do bloco de texto deve ser Automática e você deve especificar o desejado com no elemento Grade. Eu fiz isso no meu código e funciona lindamente. HTH.

varagrawal
fonte
7
<ScrollViewer Height="239" VerticalScrollBarVisibility="Auto">
    <TextBox AcceptsReturn="True" TextWrapping="Wrap" LineHeight="10" />
</ScrollViewer>

Essa é a maneira de usar o TextBox de rolagem no XAML e usá-lo como uma área de texto.

John
fonte
11
A questão está relacionada a TextBlocknão TextBox.
Afzaal Ahmad Zeeshan
Resposta não muito correta, mas achei VerticalScrollBarVisibility uma dica útil, então +1
Malachi
4

Esta resposta descreve uma solução usando MVVM.

Essa solução é ótima se você deseja adicionar uma caixa de log a uma janela, que rola automaticamente para o fundo toda vez que uma nova mensagem de log é adicionada.

Depois que essas propriedades anexadas são adicionadas, elas podem ser reutilizadas em qualquer lugar, resultando em software muito modular e reutilizável.

Adicione este XAML:

<TextBox IsReadOnly="True"   
         Foreground="Gainsboro"                           
         FontSize="13" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True"
         attachedBehaviors:TextBoxApppendBehaviors.AppendText="{Binding LogBoxViewModel.AttachedPropertyAppend}"                                       
         attachedBehaviors:TextBoxClearBehavior.TextBoxClear="{Binding LogBoxViewModel.AttachedPropertyClear}"                                    
         TextWrapping="Wrap">

Adicione esta propriedade anexada:

public static class TextBoxApppendBehaviors
{
    #region AppendText Attached Property
    public static readonly DependencyProperty AppendTextProperty =
        DependencyProperty.RegisterAttached(
            "AppendText",
            typeof (string),
            typeof (TextBoxApppendBehaviors),
            new UIPropertyMetadata(null, OnAppendTextChanged));

    public static string GetAppendText(TextBox textBox)
    {
        return (string)textBox.GetValue(AppendTextProperty);
    }

    public static void SetAppendText(
        TextBox textBox,
        string value)
    {
        textBox.SetValue(AppendTextProperty, value);
    }

    private static void OnAppendTextChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue == null)
        {
            return;
        }

        string toAppend = args.NewValue.ToString();

        if (toAppend == "")
        {
            return;
        }

        TextBox textBox = d as TextBox;
        textBox?.AppendText(toAppend);
        textBox?.ScrollToEnd();
    }
    #endregion
}

E esta propriedade anexada (para limpar a caixa):

public static class TextBoxClearBehavior
{
    public static readonly DependencyProperty TextBoxClearProperty =
        DependencyProperty.RegisterAttached(
            "TextBoxClear",
            typeof(bool),
            typeof(TextBoxClearBehavior),
            new UIPropertyMetadata(false, OnTextBoxClearPropertyChanged));

    public static bool GetTextBoxClear(DependencyObject obj)
    {
        return (bool)obj.GetValue(TextBoxClearProperty);
    }

    public static void SetTextBoxClear(DependencyObject obj, bool value)
    {
        obj.SetValue(TextBoxClearProperty, value);
    }

    private static void OnTextBoxClearPropertyChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if ((bool)args.NewValue == false)
        {
            return;
        }

        var textBox = (TextBox)d;
        textBox?.Clear();
    }
}   

Então, se você estiver usando uma estrutura de injeção de dependência como o MEF, poderá colocar todo o código específico do log em seu próprio ViewModel:

public interface ILogBoxViewModel
{
    void CmdAppend(string toAppend);
    void CmdClear();

    bool AttachedPropertyClear { get; set; }

    string AttachedPropertyAppend { get; set; }
}

[Export(typeof(ILogBoxViewModel))]
public class LogBoxViewModel : ILogBoxViewModel, INotifyPropertyChanged
{
    private readonly ILog _log = LogManager.GetLogger<LogBoxViewModel>();

    private bool _attachedPropertyClear;
    private string _attachedPropertyAppend;

    public void CmdAppend(string toAppend)
    {
        string toLog = $"{DateTime.Now:HH:mm:ss} - {toAppend}\n";

        // Attached properties only fire on a change. This means it will still work if we publish the same message twice.
        AttachedPropertyAppend = "";
        AttachedPropertyAppend = toLog;

        _log.Info($"Appended to log box: {toAppend}.");
    }

    public void CmdClear()
    {
        AttachedPropertyClear = false;
        AttachedPropertyClear = true;

        _log.Info($"Cleared the GUI log box.");
    }

    public bool AttachedPropertyClear
    {
        get { return _attachedPropertyClear; }
        set { _attachedPropertyClear = value; OnPropertyChanged(); }
    }

    public string AttachedPropertyAppend
    {
        get { return _attachedPropertyAppend; }
        set { _attachedPropertyAppend = value; OnPropertyChanged(); }
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

Veja como funciona:

  • O ViewModel alterna as propriedades anexadas para controlar o TextBox.
  • Como está usando "Anexar", é extremamente rápido.
  • Qualquer outro ViewModel pode gerar mensagens de log chamando métodos no ViewModel de log.
  • À medida que usamos o ScrollViewer embutido no TextBox, podemos fazer com que ele role automaticamente para a parte inferior da caixa de texto sempre que uma nova mensagem é adicionada.
Contango
fonte
4
<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

Estou fazendo isso de outra maneira, colocando MaxHeight no ScrollViewer.

Basta ajustar o MaxHeight para mostrar mais ou menos linhas de texto. Fácil.

Tony Wu
fonte
1

Tentei fazer com que essas sugestões funcionassem para um bloco de texto, mas não consegui fazê-lo funcionar. Eu até tentei fazê-lo funcionar do designer. (Procure em Layout e expanda a lista clicando na seta para baixo "V" na parte inferior). Tentei definir o visualizador de rolagem como Visível e depois Automático , mas ainda assim não funcionaria.

Acabei desistindo e mudei TextBlockpara para TextBoxcom o conjunto de atributos Readonly , e funcionou como um encanto.

Scott Bordelon
fonte
0

Não sei se alguém tem esse problema, mas envolvendo-o TextBlockem uma ScrollViewerinterface do usuário de alguma maneira bagunçada - como uma solução alternativa simples, descobri que substituir o TextBlockpor um TextBoxcomo esse

<TextBox  Name="textBlock" SelectionBrush="Transparent" Cursor="Arrow" IsReadOnly="True" Text="My Text" VerticalScrollBarVisibility="Auto">

cria um TextBoxque se parece e se comporta como um TextBlockcom uma barra de rolagem (e você pode fazer tudo isso no designer).

dunkleosteus
fonte
0

Esta é uma solução simples para essa pergunta. A rolagem vertical será ativada apenas quando o texto exceder o limite.

<TextBox Text="Try typing some text here " ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="WrapWithOverflow" />

Zuhair
fonte