42

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

Is there a way to do it in XAML?

6 Answers 6

77

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

Sign up to request clarification or add additional context in comments.

Comments

12

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

3 Comments

Definitely the right answer to specify via XAML only !
I've tried this approach, but the mouse scroll-wheel action is inverted. Is this just me? If not, what's the fix?
I guess you have to create a custom ScrollViewer, which handles the OnPreviewMouseWheel(MouseWheelEventArgs e) event, and inverts the Delta property. In my opinion this solution ends up being worse than just choosing to ScrollToEnd()... You still have C# codebehind, which was trying to be avoided.
9

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.

Comments

7

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

2 Comments

What is "TbEvents"?
@morknox your TextBox reference
1

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.