2011-12-03 9 views
32

Ho uno TextBlock il cui contenuto è associato a una proprietà stringa di ViewModel. Questo TextBlock ha un ScrollViewer avvolto attorno ad esso.Come scorrere fino alla fine di un ScrollViewer automaticamente con Xaml e rilegatura?

Quello che voglio fare è che ogni volta che i registri cambiano, lo ScrollViewer scorrerà verso il basso. Idealmente voglio qualcosa di simile:

<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto" 
        ScrollPosition="{Binding Path=ScrollPosition}"> 
     <TextBlock Text="{Binding Path=Logs}"/> 
    </ScrollViewer> 

mi non voglio usare codice sottostante! La soluzione che sto cercando dovrebbe utilizzare solo binding e/o Xaml.

+2

alcun motivo specifico di nessun codice dietro? –

+6

Immagino che sia una convinzione religiosa che codice dietro e MVVM non si mescoleranno. –

+3

Hai ragione ma secondo me MVVM suggerisce solo che la tua Business Logic (Vedi modello) non deve essere mescolata con la tua interfaccia utente (vista). Scroll Viewer è UI/View se mettiamo un po 'di codice nel codice per spostare ScrollViewer in basso non sarà contro MVVM perché stiamo solo giocando con l'interfaccia utente –

risposta

42

È possibile creare una proprietà associata o un comportamento per ottenere ciò che si desidera senza utilizzare il codice sottostante. In entrambi i casi sarà comunque necessario scrivere del codice.

Ecco un esempio di utilizzo della proprietà associata.

Attached Property

public static class Helper 
{ 
    public static bool GetAutoScroll(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollProperty); 
    } 

    public static void SetAutoScroll(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollProperty, value); 
    } 

    public static readonly DependencyProperty AutoScrollProperty = 
     DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(Helper), new PropertyMetadata(false, AutoScrollPropertyChanged)); 

    private static void AutoScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var scrollViewer = d as ScrollViewer; 

     if (scrollViewer != null && (bool)e.NewValue) 
     { 
      scrollViewer.ScrollToBottom(); 
     } 
    } 
} 

Xaml Binding

<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../> 

dovrai creare una proprietà booleana IsLogsChangedPropertyInViewModel e impostarla su true quando la proprietà della stringa viene modificata.

Spero che questo aiuti! :)

+0

Sfortunatamente, non ho potuto farlo funzionare sulla mia macchina, usando VS 2012 + MVVM Light. La mia ipotesi è che probabilmente dipende da un riferimento che non è documentato. Ho postato una risposta dal blog di Geoff che ha funzionato per me. – Contango

+1

La risposta di Julien XL funziona perfettamente per me. Ho VS 2012 + Mahapps. Non uso MVVM Light – Zougi

+1

@Zougi Funziona anche per me, VS2015 + MVVM Light. Soluzione Gread! – LueTm

5

E 'facile, esempi:

yourContronInside.ScrollOwner.ScrollToEnd(); 
yourContronInside.ScrollOwner.ScrollToBottom(); 
+1

Considera l'espansione della risposta per spiegare al richiedente _why_ questo raggiunge il risultato desiderato, eventualmente il collegamento alla documentazione. Come è, questo è solo marginalmente utile. –

+1

Non mi sembra una soluzione MVVM. Il trucco è farlo senza usare il codice dietro .. – ecth

10

Da Geoff's Blog on ScrollViewer AutoScroll Behavior.

Aggiungi questa classe:

namespace MyAttachedBehaviors 
{ 
    /// <summary> 
    ///  Intent: Behavior which means a scrollviewer will always scroll down to the bottom. 
    /// </summary> 
    public class AutoScrollBehavior : Behavior<ScrollViewer> 
    { 
     private double _height = 0.0d; 
     private ScrollViewer _scrollViewer = null; 

     protected override void OnAttached() 
     { 
      base.OnAttached(); 

      this._scrollViewer = base.AssociatedObject; 
      this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated); 
     } 

     private void _scrollViewer_LayoutUpdated(object sender, EventArgs e) 
     { 
      if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1) 
      { 
       this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight); 
       this._height = this._scrollViewer.ExtentHeight; 
      } 
     } 

     protected override void OnDetaching() 
     { 
      base.OnDetaching(); 

      if (this._scrollViewer != null) 
      { 
       this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated); 
      } 
     } 
    } 
} 

Questo codice dipende Comportamenti Blend, che richiedono un riferimento a System.Windows.Interactivity. Vedi help on adding System.Windows.Interactivity.

Se si installa il pacchetto MVVM Luce NuGet, è possibile aggiungere un riferimento qui:

packages\MvvmLightLibs.4.2.30.0\lib\net45\System.Windows.Interactivity.dll 

Assicurarsi di disporre di questa proprietà nella tua intestazione, che punta a System.Windows.Interactivity.dll:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 

Add un comportamento di fusione nel ScrollViewer:

<i:Interaction.Behaviors> 
    <implementation:AutoScrollBehavior /> 
</i:Interaction.Behaviors> 

Esempio:

<GroupBox Grid.Row="2" Header ="Log"> 
    <ScrollViewer> 
     <i:Interaction.Behaviors> 
      <implementation:AutoScrollBehavior /> 
     </i:Interaction.Behaviors> 
     <TextBlock Margin="10" Text="{Binding Path=LogText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap"/> 
    </ScrollViewer> 
</GroupBox> 

dobbiamo aggiungere una definizione per lo spazio dei nomi, altrimenti non saprà dove trovare la classe C# che abbiamo appena aggiunto. Aggiungi questa proprietà nel tag <Window>. Se stai usando ReSharper, lo suggerirà automaticamente per te.

xmlns:implementation="clr-namespace:MyAttachedBehaviors" 

Ora, se tutto va bene, il testo nella casella scorrerà sempre verso il basso.

L'esempio XAML fornito stamperà il contenuto della proprietà associata LogText sullo schermo, che è perfetto per la registrazione.

+0

Se aiuta qualcuno che usa il tuo campione, puoi ottenere System.Windows.Interactivity.dll per .NET 4.5 da Visual Studio 2012 o 2013 installa cartelle se hai installato Blend con uno di quelle versioni da quando Blend arriva con loro. –

+0

@Alex Marshall. Hai assolutamente ragione, grazie per aver aggiunto questa nota. Quando usavo MVVM Light non riuscivo a farlo funzionare fino a quando non ho usato l'esatto 'System.Windows.Interactivity.dll 'che è stato fornito con MVVM Light (come indicato nella risposta). Se fossero stati utilizzati altri framework MVVM o addirittura codice retrostante, probabilmente questo funzionerebbe perfettamente. In altre parole, non puoi aggiungere più versioni di questo '.dll' al tuo progetto, se il tuo framework MVVM include già. – Contango

+0

È possibile aggiungere la parte di XAML in cui si imposta la risorsa? –

15

La risposta aggiornata 2017-12-13 ora utilizza l'evento ScrollChanged e verifica se le dimensioni dell'estensione cambiano. Più affidabili e non interferisce con lo scorrimento manuale

So che questa domanda è vecchio, ma ho una migliore attuazione:

  • Nessun dipendenze esterne
  • È sufficiente impostare la proprietà una volta

Il codice è fortemente influenzata sia Justin XL e le soluzioni di riporto

public static class AutoScrollBehavior 
{ 
    public static readonly DependencyProperty AutoScrollProperty = 
     DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollBehavior), new PropertyMetadata(false, AutoScrollPropertyChanged)); 


    public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
    { 
     var scrollViewer = obj as ScrollViewer; 
     if(scrollViewer != null && (bool)args.NewValue) 
     { 
      scrollViewer.SizeChanged += ScrollViewer_ScrollChanged; 
      scrollViewer.ScrollToEnd(); 
     } 
     else 
     { 
      scrollViewer.ScrollChanged-= ScrollViewer_ScrollChanged; 
     } 
    } 

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
    { 
     // Only scroll to bottom when the extent changed. Otherwise you can't scroll up 
     if (e.ExtentHeightChange != 0) 
     { 
      var scrollViewer = sender as ScrollViewer; 
      scrollViewer?.ScrollToBottom(); 
     } 
    } 

    public static bool GetAutoScroll(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollProperty); 
    } 

    public static void SetAutoScroll(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollProperty, value); 
    } 
} 

Usage:

<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace 
+0

Vedo un'eccezione di riferimento null in attesa di accadere se questa proprietà è inserita in qualcosa che non è un ScrollViewer. –

+0

Dove esattamente? Sto usando l'operatore Elvis e il cast "come" nei casi in cui mi aspetto un ScrollViewer. –

+2

@RoyT all'interno del metodo 'AutoScrollPropertyChanged',' else' può essere raggiunto quando 'scrollViewer' è nullo, non solo quando' scrollViewer' non è 'null'e' NewValue' è 'false' – cgijbels

-1

usavo risposta @Roy T. s', ma ho voluto la stipula aggiunto che se si è scorso indietro nel tempo, ma poi aggiunto del testo, la vista di scorrimento dovrebbe auto scorrimento verso il basso .

ho usato questo:

private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
{ 
    var scrollViewer = sender as ScrollViewer; 

    if (e.ExtentHeightChange > 0) 
    { 
     scrollViewer.ScrollToEnd(); 
    }  
} 

nel luogo della manifestazione SizeChanged.

+0

Lo lego come un comportamento nel mio XAML. In questo modo: '' – cabusto

0

Ecco una piccola variazione.

Questo scorrerà verso il basso sia quando l'altezza del visore di scorrimento (vista) e l'altezza del contenuto (estensione) del presentatore di scorrimento cambiano.

Si basa sulla risposta di Roy T, ma non ho potuto commentare così ho postato una risposta.

public static class AutoScrollHelper 
    { 
     public static readonly DependencyProperty AutoScrollProperty = 
      DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollHelper), new PropertyMetadata(false, AutoScrollPropertyChanged)); 


     public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
     { 
      var scrollViewer = obj as ScrollViewer; 
      if (scrollViewer == null) return; 

      if ((bool) args.NewValue) 
      { 
       scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; 
       scrollViewer.ScrollToEnd(); 
      } 
      else 
      { 
       scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; 
      } 
     } 

     static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
     { 
      // Remove "|| e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0" if you want it to only scroll to the bottom when it increases in size 
      if (e.ViewportHeightChange > 0 || e.ExtentHeightChange > 0 || e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0) 
      { 
       var scrollViewer = sender as ScrollViewer; 
       scrollViewer?.ScrollToEnd(); 
      } 
     } 

     public static bool GetAutoScroll(DependencyObject obj) 
     { 
      return (bool) obj.GetValue(AutoScrollProperty); 
     } 

     public static void SetAutoScroll(DependencyObject obj, bool value) 
     { 
      obj.SetValue(AutoScrollProperty, value); 
     } 
    } 
Problemi correlati