我的目标是为flowdocumentscrollviewer创建一个可重用的附加行为,以便每当flowdocument被更新(追加)时,viewer自动滚动到末尾。
目前的问题:
onenabledchanged在可视化树完成之前被调用,因此找不到scrollviewer
我不知道如何附加到包含流文档的DependencyProperty。我的计划是使用It's Changed事件初始化managedRange属性。(如果需要,第一次手动触发。)
我不知道如何从range\u changed方法中获取scrollviewer属性,因为它没有dependencyobject。
我意识到这可能是3个独立的问题(又名。问题)。然而,它们是相互依赖的,以及我为这种行为所尝试的总体设计。我只是问一个问题,以防我做错了。如果我是,正确的方法是什么?
/// Attached Dependency Properties not shown here:
/// bool Enabled
/// DependencyProperty DocumentProperty
/// TextRange MonitoredRange
/// ScrollViewer ScrollViewer
public static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d == null || System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
return;
DependencyProperty documentProperty = null;
ScrollViewer scrollViewer = null;
if (e.NewValue is bool && (bool)e.NewValue)
{
// Using reflection so that this will work with similar types.
FieldInfo documentFieldInfo = d.GetType().GetFields().FirstOrDefault((m) => m.Name == "DocumentProperty");
documentProperty = documentFieldInfo.GetValue(d) as DependencyProperty;
// doesn't work. the visual tree hasn't been built yet
scrollViewer = FindScrollViewer(d);
}
if (documentProperty != d.GetValue(DocumentPropertyProperty) as DependencyProperty)
d.SetValue(DocumentPropertyProperty, documentProperty);
if (scrollViewer != d.GetValue(ScrollViewerProperty) as ScrollViewer)
d.SetValue(ScrollViewerProperty, scrollViewer);
}
private static ScrollViewer FindScrollViewer(DependencyObject obj)
{
do
{
if (VisualTreeHelper.GetChildrenCount(obj) > 0)
obj = VisualTreeHelper.GetChild(obj as Visual, 0);
else
return null;
}
while (!(obj is ScrollViewer));
return obj as ScrollViewer;
}
public static void OnDocumentPropertyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
DependencyProperty dp = e.OldValue as DependencyProperty;
// -= OnFlowDocumentChanged
}
if (e.NewValue != null)
{
DependencyProperty dp = e.NewValue as DependencyProperty;
// += OnFlowDocumentChanged
// dp.AddOwner(typeof(AutoScrollBehavior), new PropertyMetadata(OnFlowDocumentChanged));
// System.ArgumentException was unhandled by user code Message='AutoScrollBehavior'
// type must derive from DependencyObject.
}
}
public static void OnFlowDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextRange range = null;
if (e.NewValue != null)
{
FlowDocument doc = e.NewValue as FlowDocument;
if (doc != null)
range = new TextRange(doc.ContentStart, doc.ContentEnd);
}
if (range != d.GetValue(MonitoredRangeProperty) as TextRange)
d.SetValue(MonitoredRangeProperty, range);
}
public static void OnMonitoredRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
TextRange range = e.OldValue as TextRange;
if (range != null)
range.Changed -= new EventHandler(range_Changed);
}
if (e.NewValue != null)
{
TextRange range = e.NewValue as TextRange;
if (range != null)
range.Changed -= new EventHandler(range_Changed);
}
}
static void range_Changed(object sender, EventArgs e)
{
// need ScrollViewer!!
}
最佳答案
onenabledchanged在之前被调用
可视化树已完成,因此
找不到滚动查看器
在构建可视化树之后,使用Dispatcher.BeginInvoke将其余工作以异步方式排队。您还需要调用ApplyTemplate以确保模板已实例化:
d.Dispatcher.BeginInvoke(new Action(() =>
{
((FrameworkElement)d).ApplyTemplate();
d.SetValue(ScrollViewerProperty, FindScrollViewer(d));
}));
注意,您不需要检查新值是否与旧值不同。在设置依赖项属性时,框架会为您处理该问题。
您还可以使用FrameworkTemplate.FindName从flowdocumentscrollviewer获取scrollviewer。FlowDocumentScrollViewer有一个名为模板类型的ScRelVIEW部分,称为PARTION COSTESTOST,这是它实际承载内容的地方。如果查看器是重新模板化的,并且有多个scrollviewer作为子级,则这可能更准确。
var control = d as Control;
if (control != null)
{
control.Dispatcher.BeginInvoke(new Action(() =>
{
control.ApplyTemplate();
control.SetValue(ScrollViewerProperty,
control.Template.FindName("PART_ContentHost", control)
as ScrollViewer);
}));
}
我不知道该如何连接到
包含
流程文档。我的计划是用它
更改事件以初始化
managedRange属性。(手动
首次触发,如果
需要)。
框架中无法从任意依赖项属性获取属性更改通知。但是,您可以创建自己的DependencyProperty,并将其绑定到要观看的内容。有关更多信息,请参见Change Notification for Dependency Properties。
创建依赖项属性:
private static readonly DependencyProperty InternalDocumentProperty =
DependencyProperty.RegisterAttached(
"InternalDocument",
typeof(FlowDocument),
typeof(YourType),
new PropertyMetadata(OnFlowDocumentChanged));
并将onenabledchanged中的反射代码替换为:
BindingOperations.SetBinding(d, InternalDocumentProperty,
new Binding("Document") { Source = d });
当flowdocumentscrollviewer的document属性更改时,绑定将更新internaldocument,并调用onflowdocumentchanged。
我不知道怎么去
中的ScrollViewer属性
范围改变了方法,因为它没有
拥有DependencyObject。
sender属性将是一个textrange,因此您可以使用
((TextRange)sender).Start.Parent
获取dependencyobject,然后在可视化树上漫游。更简单的方法是使用lambda表达式捕获onmonitoredrangechanged中的
d
变量,方法如下:range.Changed += (sender, args) => range_Changed(d);
然后创建一个包含dependencyobject的range\u changed重载。不过,这样做会使您在完成后删除处理程序变得更加困难。
另外,尽管对Detect FlowDocument Change and Scroll的回答是textrange.changed可以工作,但我在测试时并没有看到它真正启动。如果它对您不起作用并且您愿意使用反射,则会有一个textcontainer.changed事件似乎确实会触发:
var container = doc.GetType().GetProperty("TextContainer",
BindingFlags.Instance | BindingFlags.NonPublic).GetValue(doc, null);
var changedEvent = container.GetType().GetEvent("Changed",
BindingFlags.Instance | BindingFlags.NonPublic);
EventHandler handler = range_Changed;
var typedHandler = Delegate.CreateDelegate(changedEvent.EventHandlerType,
handler.Target, handler.Method);
changedEvent.GetAddMethod(true).Invoke(container, new object[] { typedHandler });
sender
参数将是textcontainer,您可以再次使用反射返回流文档:var document = sender.GetType().GetProperty("Parent",
BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(sender, null) as FlowDocument;
var viewer = document.Parent;