Igor
Igor

Reputation: 27268

How to make WPF TextBox with a scrollbar automatically scroll to the bottom when lines are added?

For example like Visual Studio's "Output" window does.

Is there a way to do it in XAML?

Upvotes: 42

Views: 39028

Answers (6)

Exemple Thales
Exemple Thales

Reputation: 1

use this attached property

#region ScrollToEnd              
public static bool GetScrollToEnd(DependencyObject obj) => (bool)obj.GetValue(ScrollToEndProperty);
public static void SetScrollToEnd(DependencyObject obj, bool value) => obj.SetValue(ScrollToEndProperty, value);
public static readonly DependencyProperty ScrollToEndProperty =
    DependencyProperty.RegisterAttached("ScrollToEnd",
        typeof(bool),
        typeof(TextBoxHelper),
        new PropertyMetadata(false
        ,(ss,ee) => 
        { 
            if(ss is TextBoxBase tb)
            {
                tb.TextChanged += (s, e) => (s as TextBoxBase).ScrollToEnd();
            }
        }));
#endregion 

and use like this <TextBox local:TextBoxHelper.ScrollToEnd="true" ....

it's solution when textbox is readonly

Upvotes: 0

NinjaPedroX
NinjaPedroX

Reputation: 63

If you have the Textbox wrapped within a ScrollViewer, like I have mine shown below:

<ScrollViewer x:Name="ConsoleOutputScrollViewer" Background="Black">
    <StackPanel>
        <TextBox x:Name="ConsoleOutputTextBox" TextChanged="ConsoleOutputTextBox_TextChanged"
                 Background="Transparent" Foreground="White" BorderThickness="0" FontSize="15"
                 Padding="5 0 0 0" FontFamily="Consolas" TextWrapping="Wrap" IsReadOnly="True"/>
        <Grid Height="100"/>
    </StackPanel>
</ScrollViewer>

You can then handle the TextChanged Event and call ScrollToEnd() like mentioned in other answers here. HOWEVER, the drawback to this is that you won't be able to view the text history when a new line is added, which can present the user with a very bad experience, especially if you have new lines being added constantly.

An easy solution to this is to check and compare the current scrollviewer VerticalOffset to the total ScrollableHeight of the ScrollViewer, as shown below:

private void ConsoleOutputTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    if (ConsoleOutputScrollViewer.VerticalOffset == ConsoleOutputScrollViewer.ScrollableHeight)
    {
        ConsoleOutputScrollViewer.ScrollToEnd();
    }
}

Upvotes: 1

Skomski
Skomski

Reputation: 4870

You can handle the TextChanged event, which will fire whenever you change that TextBox's Text: TextBoxBase.ScrollToEnd().

Upvotes: 77

LittleBit
LittleBit

Reputation: 1201

There is a way to do it in XAML, you can use this Style to display it like a Console would (Be aware of the drawbacks, it just looks like a Console but does not completely behave like it)

        <Style x:Key="ConsoleTextBox" TargetType="{x:Type TextBox}">
            <Setter Property="IsReadOnly" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <ScrollViewer RenderTransformOrigin="0.5,0.5" VerticalScrollBarVisibility="Auto">
                            <ScrollViewer.RenderTransform>
                                <ScaleTransform ScaleY="-1"/>
                            </ScrollViewer.RenderTransform>
                            <TextBox Text="{TemplateBinding Text}" RenderTransformOrigin="0.5,0.5">
                                <TextBox.RenderTransform>
                                    <ScaleTransform ScaleY="-1"/>
                                </TextBox.RenderTransform>
                            </TextBox>
                        </ScrollViewer>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

How does it work

Inside the TextBox, a ScrollViewer is flipped vertically (the "new" lines are added at the Bottom)

In the ScrollViewer, there is another Textbox which is flipped Vertically to display the Text correctly (not upside down).

Using the Style

Include it in your App.xaml or via ResourceDictionary and set the Style of the TextBox to ConsoleTextBox.

<TextBox Style="{StaticResource ConsoleTextBox}"/>

Drawbacks

  • When you copy the Text from this "Console" there will be no Line Breaks.
  • Scrolling with the Mouse is inverted

Upvotes: 12

Guillermo Ruffino
Guillermo Ruffino

Reputation: 3020

Visual Studio output window behavior is special, because it will only keep auto scrolling down if the caret is at the end of the text box, which allows you to examine the output without being disturbed if new lines are added to it.

I've got such behavior with this code

bool scrollToEnd = TbEvents.CaretIndex == TbEvents.Text.Length;
TbEvents.AppendText(text + Environment.NewLine);
if (scrollToEnd)
{
    TbEvents.CaretIndex = TbEvents.Text.Length;
    TbEvents.ScrollToEnd();
}

Upvotes: 7

bitbonk
bitbonk

Reputation: 49659

You could write an attached property or even better a behavior that listens to the TextChanged event and scrolls to the bottom in the callback.

Upvotes: 9

Related Questions