Automatic vertical scroll bar in WPF TextBlock?

2019-01-20 23:15发布

问题:

I have a TextBlock in WPF. I write many lines to it, far exceeding its vertical height. I expected a vertical scroll bar to appear automatically when that happens, but it didn't. I tried to look for a scroll bar property in the Properties pane, but could not find one.

How can I make vertical scroll bar created automatically for my TextBlock once its contents exceed its height?

Clarification: I would rather do it from the designer and not by directly writing to the XAML.

回答1:

Wrap it in a scroll viewer:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

NOTE this answer applies to a TextBlock (a read-only text element) as asked for in the original question.

If you want to show scroll bars in a TextBox (an editable text element) then use the ScrollViewer attached properties:

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

Valid values for these two properties are Disabled, Auto, Hidden and Visible.



回答2:

can use the following now:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>


回答3:

Something better would be:

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

This makes sure that the text in your textblock does not overflow and overlap the elements below the textblock as may be the case if you do not use the grid. That happened to me when I tried other solutions even though the textblock was already in a grid with other elements. Keep in mind that the width of the textblock should be Auto and you should specify the desired with in the Grid element. I did this in my code and it works beautifully. HTH.



回答4:

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

This is way to use the scrolling TextBox in XAML and use it as a text area.



回答5:

This answer describes a solution using MVVM.

This solution is great if you want to add a logging box to a window, that automatically scrolls to the bottom each time a new logging message is added.

Once these attached properties are added, they can be reused anywhere, so it makes for very modular and reusable software.

Add this 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">

Add this attached property:

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
}

And this attached property (to clear the box):

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

Then, if you're using a dependency injection framework such as MEF, you can place all of the logging-specific code into it's own 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
}

Here's how it works:

  • The ViewModel toggles the Attached Properties to control the TextBox.
  • As it's using "Append", it's lightning fast.
  • Any other ViewModel can generate logging messages by calling methods on the logging ViewModel.
  • As we use the ScrollViewer built into the TextBox, we can make it automatically scroll to the bottom of the textbox each time a new message is added.


回答6:

<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

I am doing this in another way by putting MaxHeight in ScrollViewer.

Just Adjust the MaxHeight to show more or fewer lines of text. Easy.



回答7:

You can use

ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Visible"

These are attached property of wpf. For more information

http://wpfbugs.blogspot.in/2014/02/wpf-layout-controls-scrollviewer.html



回答8:

I tried to to get these suggestions to work for a textblock, but couldn't get it to work. I even tried to get it to work from the designer. (Look in Layout and expand the list by clicking the down-arrow "V" at the bottom) I tried setting the scrollviewer to Visible and then Auto, but it still wouldn't work.

I eventually gave up and changed the TextBlock to a TextBox with the Readonly attribute set, and it worked like a charm.



回答9:

Dont know if someone else has this problem but wrapping my TextBlock into a ScrollViewer somewhow messed up my UI - as a simple workaround I figured out that replacing the TextBlock by a TextBox like this one

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

creates a TextBox that looks and behaves like a TextBlock with a scrollbar (and you can do it all in the designer).



回答10:

This is a simple solution to that question. The vertical scroll will be activated only when the text overflows.

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