我想检测(最好通过一个事件)何时在 a 中添加、更改等任何内容,FlowDocument
并且当它发生时,我想让 aFlowDocumentScrollViewer
显示FlowDocument
自动滚动到末尾。
4 回答
您可以FlowDocument
通过创建文本范围并监视其更改来检测 中的更改。滚动到底部更加困难,因为您必须找到ScrollViewer
. 同样为了性能,您不希望在每次更改时重做所有滚动计算,因此您应该使用DispatcherOperations
.
综上所述,这段代码应该可以解决问题:
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();
});
};
其中FindFirstVisualDescendantOfType
是一个简单的视觉树的深度优先前缀搜索,使用VisualTreeHelper.GetChildrenCount()
并VisualTreeHelper.GetChild()
返回找到的第一个指定类型的视觉对象。
请注意,为了完全通用,我不会在代码顶部预先计算 scrollViewer,因为FlowDocumentScrollViewer
' 的模板可以更改。如果这不会发生,则可以通过在注册事件处理程序之前调用然后计算来加速.ApplyTemplate()
此代码FlowDocumentScrollViewer
:scrollViewer
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();
});
};
请注意,我们不能简单地调用scrollViewer.GetTemplateChild("PART_ContentHost")
并跳过可视化树搜索,因为GetTemplateChild
它是受保护的。
您是否使用 RichTextBox 进行编辑?如果是这样,您应该能够挂钩事件,然后使用TextChanged
属性中的ScrollToVerticalOffset
值调用方法。ViewportHeight
连接到 TextChanged 事件后,您可以简单地使用:
// Showing Last Block
YourReader.Document.Blocks.LastBlock.BringIntoView();
// Or.. showing the last Inline
(YourReader.Document.Blocks.LastBlock as Paragraph).Inlines.LastInline.BringIntoView();
但是,这仅适用于 FlowDocumentPageViewer,也适用于 FlowDocumentReader(带有页面 ViewingModes),对于 FlowDocumentScrollViewer,您应该使用提到的可视化树
public static ScrollViewer FindScroll(Visual visual)
{
if (visual is ScrollViewer)
return visual as ScrollViewer;
ScrollViewer searchChiled = null;
DependencyObject chiled;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
chiled = VisualTreeHelper.GetChild(visual, i);
if (chiled is Visual)
searchChiled = FindScroll(chiled as Visual);
if (searchChiled != null)
return searchChiled;
}
return null;
}
ScrollViewer scroller = FindScroll(YourReader as Visual);
if (scroller != null)
(scroller as ScrollViewer).ScrollToBottom();
您可以使用以下扩展方法来获取内部滚动查看器:
public static class FlowDocumentScrollViewerExtensions
{
public static ScrollViewer GetScrollViewer(this FlowDocumentScrollViewer element) {
if (element == null) {
throw new ArgumentNullException(nameof(element));
}
return element.Template?.FindName("PART_ContentHost", element) as ScrollViewer;
}
}
此外,您可以在添加内容之前使用这些扩展方法来检查滚动查看器本身的滚动位置(如果您想要滚动 - 仅 - 如果滚动查看器已经位于末尾):
public static class ScrollViewerExtensions
{
public static bool IsAtHome(this ScrollViewer element) {
if (element == null) {
throw new ArgumentNullException(nameof(element));
}
return element.VerticalOffset <= 0;
}
public static bool IsAtEnd(this ScrollViewer element) {
if (element == null) {
throw new ArgumentNullException(nameof(element));
}
return element.VerticalOffset >= element.ScrollableHeight;
}
}
之后只需调用 scrollViewer.ScrollToEnd() 例如。