Come creare un comportamento allegato per lo scorrimento automatico di un flowdocumentscrollviewer

StackOverflow https://stackoverflow.com/questions/3300969

Domanda

Il mio obiettivo è creare un comportamento allegato riutilizzabile per un flowdocumentCrollViewer, in modo che il visualizzatore scorre automaticamente fino all'estremità ogni volta che il flowdocument è stato aggiornato (aggiunto).

Problemi finora:

    .
  • onenbledChanged viene chiamato prima che l'albero visivo sia completato, e quindi non trova lo scrollviewer
  • Non so come allegare alla fabbrica di dipendenza contenente il flowdocument. Il mio piano doveva usare il suo evento modificato per inizializzare la proprietà gestire. (Attivato manualmente per la prima volta, se necessario.)
  • Non so come arrivare alla proprietà Scrollviewer dal metodo della gamma_Changed, in quanto non ha la dipendenzaObject.

Mi rendo conto che quelli sono potenzialmente 3 problemi separati (alias. Domande). Tuttavia dipendono l'uno dall'altro e il design complessivo che ho tentato per questo comportamento. Lo sto chiedendo come una sola domanda nel caso in cui sto andando in questo modo sbagliato. Se lo sono, qual è il modo giusto?

/// 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!!
}
.

È stato utile?

Soluzione

.

onenbledchanged viene chiamato prima L'albero visivo è completato, e quindi non trova lo scrollviewer

Usa dispatcher.begininvoke per enqueque il resto del lavoro Per accadre asincrono, dopo che l'albero visivo è stato costruito. Avrai anche bisogno di chiamare ApplicationTemplate a Assicurarsi che il modello sia stato istaniato:

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

Nota che non è necessario verificare se il nuovo valore è diverso da quello vecchio. Il framework gestisce che per te quando si impostano proprietà di dipendenza.

Potresti anche usare frameworktemplate.FindName per ottenere lo scrollviewer dal flowdocumentscrollviewer. FlowDocumentsCrolleViewer ha una parte del modello denominata di tipo ScrollViewer chiamato Part_Contentnenthost che è dove in realtà ospiterà il contenuto. Questo può essere più accurato nel caso in cui lo spettatore sia riportato e ha più di uno scrollviewer da bambino.

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);
    }));
}
.

.

Non so come allegare al Dependyproperty contenente il Flowdocument. Il mio piano era di usarlo è Evento modificato per inizializzare il ManagedRange Property. (Manualmente innescato per la prima volta se necessario.)

Non c'è modo di integrato nel framework per ottenere la notifica modificata dalla proprietà da una proprietà di dipendenza arbitraria. Tuttavia, puoi creare la tua dipendenzaProperty e semplicemente legarlo a quello che vuoi guardare. Vedi Modifica notifica per le proprietà di dipendenza per altro informazione.

Crea una proprietà di dipendenza:

private static readonly DependencyProperty InternalDocumentProperty = 
    DependencyProperty.RegisterAttached(
        "InternalDocument",
        typeof(FlowDocument),
        typeof(YourType),
        new PropertyMetadata(OnFlowDocumentChanged));
.

e sostituisci il codice di riflessione in OnNabledChanged con semplicemente:

BindingOperations.SetBinding(d, InternalDocumentProperty, 
    new Binding("Document") { Source = d });
.

Quando la proprietà del documento delle modifiche a flowdocumenscrollViewer, il binding si aggiornerà internaldocument, e verrà chiamato onflowdocumentchanged.

.

Non so come arrivare al Proprietà Scrollviewer da dentro Metodo della gamma_canged, come non lo fa avere la dipendenzaObject.

La proprietà del mittente sarà un TEXTRANGE, in modo da poter utilizzare ((TextRange)sender).Start.Parent per ottenere una dipendenza e quindi camminare sull'albero visivo.

Un metodo più semplice sarebbe quello di utilizzare un'espressione Lambda per catturare la variabile d in OnMonitoreDrangeChanged facendo qualcosa del genere:

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

e quindi creare un sovraccarico di gamma_canged che assume una dipendenza. Ciò renderà un po 'più difficile rimuovere il gestore quando hai finito, però.

Anche, anche se la risposta a Rileva il cambio di flusso e il rotolo di flusso afferma che Textrange. Cambiato funzionerà, non vedrò davvero sparare quando l'ho testato. Se non funziona per te e sei disposto ad usare la riflessione, c'è un evento TextContainer.Changed che sembra sparare:

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 });
.

Il parametro sender sarà il TextContainer e puoi utilizzare nuovamente la riflessione per tornare al flowdocument:

var document = sender.GetType().GetProperty("Parent", 
    BindingFlags.Instance | BindingFlags.NonPublic)
    .GetValue(sender, null) as FlowDocument;
var viewer = document.Parent;
.

Altri suggerimenti

Aiuto?

È un buon inizio almeno (forse?).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top