47

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.

starball
  • 20,030
  • 7
  • 43
  • 238
Jaded
  • 1,802
  • 6
  • 25
  • 38
  • When you say you need to "scroll to [the] bottom of [the] textbos", do you really mean "scrolling" as in "the last appended text is completely visible"? Or do you want the caret to be at the very end of the textbox? – Daniel Hilgarth Jan 20 '12 at 09:14
  • First. To illustrate made a screenshot - left is what i get, right is what i need (as manually scrolling down) : http://i.piccy.info/i7/0c105234c75b7031df050587f72771b4/1-5-3848/56682026/120120114019_6.jpg – Jaded Jan 20 '12 at 09:52

4 Answers4

88

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"/>
Community
  • 1
  • 1
Adrian Fâciu
  • 12,414
  • 3
  • 53
  • 68
  • Tried that. Caret indeed is after last line in textbox, but scrolling to it doesn't occur, i still see top lines after application run (see left part of image in above comment). – Jaded Jan 20 '12 at 09:50
  • 1
    I've added an example of textbox that i'm using and the scroll works. You might need to add the TextWrapping property. – Adrian Fâciu Jan 20 '12 at 09:55
  • Looks like the problem is the fact that textbox doesn't have chance to refresh/invalidate itself when operations are executed after application is loaded (in page constructor). When i put logic in button handler and execute it manually scroll indeed works like a charm. *edit* Loaded += (sender, args) => TestOperations() in page ctor did the trick. Thanks for help. The only thing i would like to know what method i could execute manually to achieve same effect and still leave code in page constructor. – Jaded Jan 20 '12 at 10:11
  • Are you executing your code before or after the call to `InitializeComponents`? – Daniel Hilgarth Jan 20 '12 at 10:22
  • I've added the code in the page constructor after the InitializeComponents method and it still works ok for me. – Adrian Fâciu Jan 20 '12 at 10:26
  • No, code was after InitializeComponents like this : InitializeComponent(); ShowsNavigationUI = false; const bool debugMode = true; if (debugMode) { TestOperations(); } // app requirement – Jaded Jan 20 '12 at 13:22
19

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.

Lee Willis
  • 1,552
  • 9
  • 14
  • 2
    +1 Much more simple than other posts on the same subject I even kept only OnTextChanged (... ) {base.OnTextChanged(e);ScrollToEnd();} because for my simple application it was sufficient. – NGI Sep 11 '15 at 13:41
  • The accepted answer works, but this is the way to do it if you are following MVVM. – Trevor Vance Jun 15 '22 at 13:52
  • No need to make a custom control, just add ((TextBox)sender).CaretIndex = ((TextBox)sender).Text.Length; ((TextBox)sender).ScrollToEnd(); to the TextChanged event handler of the TextBox. – Sinan Ceylan Nov 01 '22 at 16:31
12

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 && 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

itsho
  • 4,640
  • 3
  • 46
  • 68
yan yankelevich
  • 885
  • 11
  • 25
2

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);
Darko D.
  • 231
  • 1
  • 2
  • 4