There are a lot of similiar questions over internet, on SO included, but proposed solutions doesn't work in my case.
Scenario : there is a log textbox in xaml
<TextBox Name="Status"
Margin="5"
Grid.Column="1"
Grid.Row="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="600"
Height="310"/>
There are methods in code-behind that do some work and add some multiline (maybe that's the problem?) messages into this textbox:
private static void DoSomeThings(TextBox textBox)
{
// do work
textBox.AppendText("Work finished\r\n"); // better way than Text += according to msdn
// do more
textBox.AppendText("One more message\r\n");
...
}
private static void DoSomething2(TextBox textBox)
{
// same as first method
}
Need to scroll to bottom of textbox after all actions take place. Tried ScrollToEnd(), ScrollToLine, wrapping textbox into ScrollViewer, Selection and Caret workarounds, attaching ScrollToEnd to TextChanged. None of this works, after execution lines that overflow textbox height still need to be scrolled to manually. Sorry for duplicate question, i guess i'm missing some minor issues that can be resolved quickly by someone that has fresh vision on the problem. Thanks in advance.
According to this question: TextBox.ScrollToEnd doesn't work when the TextBox is in a non-active tab
You have to focus the text box, update the caret position and then scroll to end:
Status.Focus();
Status.CaretIndex = Status.Text.Length;
Status.ScrollToEnd();
EDIT
Example TextBox:
<TextBox TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
AcceptsReturn="True" Name="textBox"/>
If you make it into a simple custom control then you don't need any code behind to do the scrolling.
public class ScrollingTextBox : TextBox {
protected override void OnInitialized (EventArgs e) {
base.OnInitialized(e);
VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
}
protected override void OnTextChanged (TextChangedEventArgs e) {
base.OnTextChanged(e);
CaretIndex = Text.Length;
ScrollToEnd();
}
}
If you're using WPF it would be far better to use binding rather than passing the text box around in the code behind.
If you don't like code behind to much, here is an AttachedProperty that will do the trick :
namespace YourProject.YourAttachedProperties
{
public class TextBoxAttachedProperties
{
public static bool GetAutoScrollToEnd(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToEndProperty);
}
public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToEndProperty, value);
}
// Using a DependencyProperty as the backing store for AutoScrollToEnd. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoScrollToEndProperty =
DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(TextBoxAttachedProperties), new PropertyMetadata(false, AutoScrollToEndPropertyChanged));
private static void AutoScrollToEndPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is TextBox textbox && e.NewValue is bool mustAutoScroll)
{
textbox.TextChanged += (s, ee)=> AutoScrollToEnd(s, ee, textbox);
}
}
private static void AutoScrollToEnd(object sender, TextChangedEventArgs e, TextBox textbox)
{
textbox.ScrollToEnd();
}
}
}
And then in your xaml just do :
<TextBox
AcceptsReturn="True"
myAttachedProperties:TextBoxAttachedProperties.AutoScrollToEnd="True"/>
Just don't forget to add at the top of your xaml file
xmlns:myAttachedProperties="clr-namespace:YourProject.YourAttachedProperties"
And voila
Thanks! I have added this to remember the original focus:
var oldFocusedElement = FocusManager.GetFocusedElement(this);
this.textBox.Focus();
this.textBox.CaretIndex = this.textBox.Text.Length;
this.textBox.ScrollToEnd();
FocusManager.SetFocusedElement(this, oldFocusedElement);