2009-10-31 15 views
5

Desidero rilevare (preferibilmente attraverso un evento) quando un contenuto è aggiunto, modificato, ecc. In un FlowDocument e quando lo fa voglio un FlowDocumentScrollViewer visualizzare lo FlowDocument per scorrere automaticamente fino alla fine .Detect FlowDocument Change and Scroll

risposta

7

È possibile rilevare le modifiche nel FlowDocument creando un intervallo di testo e monitorandolo per le modifiche. Scorrere verso il basso è più difficile perché devi trovare lo ScrollViewer. Anche per le prestazioni non si desidera ripetere tutti i calcoli di scorrimento ad ogni modifica, quindi è necessario utilizzare DispatcherOperations.

Mettere tutto insieme, questo codice dovrebbe fare il trucco:

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

dove FindFirstVisualDescendantOfType è una semplice ricerca di prefisso in profondità dell'albero visiva utilizzando VisualTreeHelper.GetChildrenCount() e VisualTreeHelper.GetChild() e restituendo il primo visiva trovato del specificato genere.

Si noti che per la generalità completa non precompasso scrollViewer nella parte superiore del codice poiché il modello di FlowDocumentScrollViewer può essere modificato. Se questo non accadrà, questo codice può essere accelerato chiamando .ApplyTemplate() sulla FlowDocumentScrollViewer e poi calcolando scrollViewer prima del gestore di eventi è registrato:

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

Nota che non possiamo semplicemente chiamare scrollViewer.GetTemplateChild("PART_ContentHost") e saltare la ricerca ad albero visuale perché GetTemplateChild è protetto.

+0

Non ho avuto bisogno di questo con il codice dell'albero visivo FlowDocument e FlowDocumentScrollViewer. Solo 2 invoca, 1 per creare e aggiungere il paragrafo da una stringa e 1 per visualizzare quel paragrafo. –

2

Una volta collegati ad un TextChanged Event, si può semplicemente utilizzare:

// Showing Last Block 
YourReader.Document.Blocks.LastBlock.BringIntoView(); 

// Or.. showing the last Inline 
(YourReader.Document.Blocks.LastBlock as Paragraph).Inlines.LastInline.BringIntoView(); 

Ma, questo funziona solo su un FlowDocumentPageViewer, ed anche su un FlowDocumentReader (con pagine ViewingModes), per FlowDocumentScrollViewer si dovrebbe usare l'albero visivo come menzionato

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(); 
+0

Sto usando solo un FlowDocument e FlowDocumentscrollViewer.La soluzione BringIntoView sopra funziona perfettamente per me. Non avevo bisogno di VisualTreeHelper La mia soluzione è usare Dispatcher.Invoke per scrivere creare il paragrafo, memorizzare quel paragrafo come variabile e poi Dispatcher.Invoke di nuovo per portarlo in vista. Questo sembra il metodo più semplice. –

0

È possibile utilizzare il seguente metodo di estensione per ottenere lo spettatore di scorrimento interno:

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

Inoltre è possibile utilizzare questi metodi di estensione prima di aggiungere contenuti per controllare la posizione di scorrimento ScrollViewer stesso (nel caso in cui si desidera scorrere - solo- se lo spettatore di scorrimento era già alla fine, per esempio):

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

Successivamente basta chiamare scrollViewer.ScrollToEnd(), per esempio.