Reputation: 27268
For example like Visual Studio's "Output" window does.
Is there a way to do it in XAML?
Upvotes: 42
Views: 39028
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
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
Reputation: 4870
You can handle the TextChanged event, which will fire whenever you change that TextBox's Text:
TextBoxBase.ScrollToEnd()
.
Upvotes: 77
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
Upvotes: 12
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
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