6

私の目標は、FlowDocumentScrollViewerの再利用可能な添付動作を作成して、FlowDocumentが更新(追加)されるたびにビューアが自動的に最後までスクロールするようにすることです。

これまでの問題:

  • OnEnabledChangedは、ビジュアルツリーが完了する前に呼び出されるため、ScrollViewerが見つかりません。
  • FlowDocumentを含むDependencyPropertyにアタッチする方法がわかりません。私の計画は、そのchangedイベントを使用してManagedRangeプロパティを初期化することでした。(必要に応じて、初めて手動でトリガーされます。)
  • DependencyObjectがないため、range_Changedメソッド内からScrollViewerプロパティにアクセスする方法がわかりません。

私はそれらが潜在的に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!!
}
4

2 に答える 2

4

OnEnabledChangedは、ビジュアルツリーが完了する前に呼び出されるため、ScrollViewerが見つかりません。

Dispatcher.BeginInvokeを使用して、ビジュアルツリーが構築された後、非同期で発生する残りの作業をキューに入れます。また、 ApplyTemplateを呼び出して、テンプレートがインスタンス化されていることを確認する必要があります。

d.Dispatcher.BeginInvoke(new Action(() =>
{
    ((FrameworkElement)d).ApplyTemplate();
    d.SetValue(ScrollViewerProperty, FindScrollViewer(d));
}));

新しい値が古い値と異なるかどうかを確認する必要はないことに注意してください。フレームワークは、依存関係のプロパティを設定するときにそれを処理します。

FrameworkTemplate.FindNameを使用して、FlowDocumentScrollViewerからScrollViewerを取得することもできます。FlowDocumentScrollViewerには、PART_ContentHostと呼ばれるタイプScrollViewerの名前付きテンプレート部分があり、実際にコンテンツをホストします。これは、ビューアが再テンプレート化され、子として複数の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);
    }));
}

FlowDocumentを含むDependencyPropertyにアタッチする方法がわかりません。私の計画は、そのchangedイベントを使用してManagedRangeプロパティを初期化することでした。(必要に応じて、初めて手動でトリガーされます。)

任意の依存関係プロパティからプロパティ変更通知を取得するためのフレームワークに組み込まれた方法はありません。ただし、独自のDependencyPropertyを作成して、監視したいものにバインドすることができます。詳細については、依存関係プロパティの変更通知を参照してください。

依存関係プロパティを作成します。

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が呼び出されます。

DependencyObjectがないため、range_Changedメソッド内からScrollViewerプロパティにアクセスする方法がわかりません。

送信者プロパティはTextRangeになるため((TextRange)sender).Start.Parent、DependencyObjectを取得してから、ビジュアルツリーを上に移動するために使用できます。

より簡単な方法は、ラムダ式を使用して、次のdようにしてOnMonitoredRangeChangedの変数をキャプチャすることです。

range.Changed += (sender, args) => range_Changed(d);

次に、DependencyObjectを取り込むrange_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 });

パラメータはsenderTextContainerになり、リフレクションを再度使用してFlowDocumentに戻ることができます。

var document = sender.GetType().GetProperty("Parent", 
    BindingFlags.Instance | BindingFlags.NonPublic)
    .GetValue(sender, null) as FlowDocument;
var viewer = document.Parent;
于 2010-08-05T02:04:49.723 に答える
0

これは役に立ちますか?

少なくとも(多分?)良いスタートです。

于 2010-08-04T16:45:42.133 に答える