I want to detect (preferably through an event) when any content is added, changed, etc. in a FlowDocument
and when it does I want to cause a FlowDocumentScrollViewer
displaying the FlowDocument
to automatically scroll to the end.
views:
343answers:
2Are you using a RichTextBox to do the editing? If so you should just be able to hook the TextChanged
event and then call the ScrollToVerticalOffset
method with the value from the ViewportHeight
property.
You can detect changes in the FlowDocument
by creating a text range and monitoring it for changes. Scrolling to the bottom is more difficult because you have to find the ScrollViewer
. Also for performance you don't want redo all the scrolling calculations on every change, so you should use DispatcherOperations
.
Putting it all together, this code should do the trick:
var range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
object operation = null;
range.Changed += (obj, e) =>
{
if(operation==null)
operation = Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
operation = null;
var scrollViewer = FindFirstVisualDescendantOfType<ScrollViewer>(flowDocument);
scrollViewer.ScrollToBottom();
});
};
where FindFirstVisualDescendantOfType
is a simple depth-first prefix search of the visual tree using VisualTreeHelper.GetChildrenCount()
and VisualTreeHelper.GetChild()
and returning the first Visual found of the specified type.
Note that for full generality I don't precompute scrollViewer at the top of the code because the FlowDocumentScrollViewer
's template can change. If this won't happen, this code can be speeded up by calling .ApplyTemplate()
on the FlowDocumentScrollViewer
and then computing scrollViewer
before the event handler is registered:
var range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
object operation = null;
flowDocument.ApplyTemplate();
var scrollViewer = FindFirstVisualDescendantOfType<ScrollViewer>(flowDocument);
range.Changed += (obj, e) =>
{
if(operation==null)
operation = Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
operation = null;
scrollViewer.ScrollToBottom();
});
};
Note that we cannot simply call scrollViewer.GetTemplateChild("PART_ContentHost")
and skip the visual tree search because GetTemplateChild
is protected.