Я долго боролся с этим в собственном приложении. Метод Dispatcher.BeginInvoke не был для меня надежным; иногда он прокручивается до дна, а иногда нет. Единственный способ заставить его работать - это вставить два вызова Dispatcher.BeginInvoke друг в друга, но мне не понравилось это решение.
Вместо использования Dispatcher я злоупотребляю обработчиками событий. В этом примере текстовое поле будет прокручиваться вниз, только если оно было там во время добавления нового Inline (полезное поведение для окон чата, позволяющее пользователям читать предыдущие сообщения, если они этого хотят). Вы можете передать объект Run в качестве параметра Inline.
private void AddInline(Inline inline)
{
var viewer = textBox.GetChildByType<ScrollViewer>(item => item.Name == "ContentElement") as ScrollViewer;
bool scrollToBottom = viewer.VerticalOffset == viewer.ScrollableHeight;
// Creating the paragraph isn't necessary.
// In my own application, I only add a single paragraph to the RichTextBox that displays my chat messages.
// Then I just add new Inlines to the single paragraph.
Paragraph p = new Paragraph();
p.Inlines.Add(inline);
if (scrollToBottom)
textBox.LayoutUpdated += ScrollRichTextBoxToBottom;
// Adding the paragraph triggers a layout update.
// In the LayoutUpdated handler, the viewer will be scrolled to the bottom, triggering a second layout pass.
textBox.Blocks.Add(p);
}
private void ScrollRichTextBoxToBottom(object sender, EventArgs e)
{
// The LayoutUpdated handler needs to be removed, otherwise the RichTextBox becomes unscrollable.
textBox.LayoutUpdated -= ScrollRichTextBoxToBottom;
var viewer = textBox.GetChildByType<ScrollViewer>(item => item.Name == "ContentElement") as ScrollViewer;
viewer.ScrollToVerticalOffset(viewer.ScrollableHeight);
}
Примечание: GetChildByType - это просто метод расширения для получения ScrollViewer. (Я не создал этот метод расширения.)
public static class Extensions
{
public static T GetChildByType<T>(this UIElement element, Func<T, bool> condition)
where T : UIElement
{
List<T> results = new List<T>();
GetChildrenByType<T>(element, condition, results);
if (results.Count > 0)
return results[0];
else
return null;
}
private static void GetChildrenByType<T>(UIElement element, Func<T, bool> condition, List<T> results)
where T : UIElement
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
UIElement child = VisualTreeHelper.GetChild(element, i) as UIElement;
if (child != null)
{
T t = child as T;
if (t != null)
{
if (condition == null)
results.Add(t);
else if (condition(t))
results.Add(t);
}
GetChildrenByType<T>(child, condition, results);
}
}
}
}