OnEnabledChanged is called before the visual tree is completed, and therefore does not find ScrollViewer
Use Dispatcher.BeginInvoke to include the rest of the work in the asynchronous process after creating the visual tree. You also need to call ApplyTemplate to ensure that the template has been created:
d.Dispatcher.BeginInvoke(new Action(() => { ((FrameworkElement)d).ApplyTemplate(); d.SetValue(ScrollViewerProperty, FindScrollViewer(d)); }));
Please note that you do not need to check if the new value is different from the old. This environment handles this for you when setting dependency properties.
You can also use FrameworkTemplate.FindName to get the ScrollViewer from the FlowDocumentScrollViewer. FlowDocumentScrollViewer has a named part of a ScrollViewer type template called PART_ContentHost where it will host the content. This may be more accurate in the case of a repeated view template and has more than one ScrollViewer as a child.
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); })); }
I do not know how to connect to a DependencyProperty containing a FlowDocument. My plan was to use its modified event to initialize the ManagedRange property. (Manually starts for the first time, if necessary.)
The structure does not have a built-in method of receiving notification of a property change from an arbitrary dependency property. However, you can create your own DependencyProperty and just bind it to the one you want to see. For more information, see Modify dependency property notification .
Create a dependency property:
private static readonly DependencyProperty InternalDocumentProperty = DependencyProperty.RegisterAttached( "InternalDocument", typeof(FlowDocument), typeof(YourType), new PropertyMetadata(OnFlowDocumentChanged));
And replace your reflection code in OnEnabledChanged simply:
BindingOperations.SetBinding(d, InternalDocumentProperty, new Binding("Document") { Source = d });
When the Document FlowDocumentScrollViewer property changes, the binding updates the InternalDocument and calls OnFlowDocumentChanged.
I do not know how to get to the ScrollViewer Property from within range_Changed, since it does not have a DependencyObject.
The sender property will be TextRange, so you can use ((TextRange)sender).Start.Parent to get a DependencyObject and then go to the visual tree.
An easier way would be to use a lambda expression to capture the variable d in OnMonitoredRangeChanged by doing something like this:
range.Changed += (sender, args) => range_Changed(d);
And then create a range_Changed overload that takes a DependencyObject. This will make it a little harder to remove the handler when you are done.
Also, although the response to FlowDocument's Change and Scroll Detection says that TextRange.Changed will work, I have not seen it really work when I tested it. If this does not work for you, and you are ready to use reflection, there is a TextContainer.Changed event, which seems to fire:
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 });
The sender parameter sender be TextContainer, and you can use reflection again to return to the FlowDocument:
var document = sender.GetType().GetProperty("Parent", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(sender, null) as FlowDocument; var viewer = document.Parent;