2010-06-06 4 views
24

Je voudrais créer le comportement suivant dans un ScrollViewer qui enveloppe ContentControl:
Lorsque la hauteur ContentControl pousse, la ScrollViewer devrait défiler automatiquement à la fin. Ceci est facile à réaliser en utilisant ScrollViewer.ScrollToEnd().
Cependant, si l'utilisateur utilise la barre de défilement, le défilement automatique ne devrait plus se produire. Ceci est similaire à ce qui se passe dans la fenêtre de sortie VS par exemple.Comment faire défiler automatiquement ScrollViewer - que si l'utilisateur n'a pas changé la position de défilement

Le problème est de savoir quand un défilement s'est produit à cause du défilement de l'utilisateur et quand c'est arrivé parce que la taille du contenu a changé. J'ai essayé de jouer avec le ScrollChangedEventArgs de ScrollChangedEvent, mais je n'ai pas réussi à le faire fonctionner.

Idéalement, je ne souhaite pas gérer tous les événements de souris et de clavier possibles.

Répondre

5

Ce code défilera automatiquement pour se terminer lorsque le contenu se développera s'il a déjà été déplacé vers le bas.

XAML:

<Window x:Class="AutoScrollTest.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Height="300" Width="300"> 
    <ScrollViewer Name="_scrollViewer"> 
     <Border BorderBrush="Red" BorderThickness="5" Name="_contentCtrl" Height="200" VerticalAlignment="Top"> 
     </Border> 
    </ScrollViewer> 
</Window> 

code derrière:

using System; 
using System.Windows; 
using System.Windows.Threading; 

namespace AutoScrollTest 
{ 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 

      DispatcherTimer timer = new DispatcherTimer(); 
      timer.Interval = new TimeSpan(0, 0, 2); 
      timer.Tick += ((sender, e) => 
       { 
        _contentCtrl.Height += 10; 

        if (_scrollViewer.VerticalOffset == _scrollViewer.ScrollableHeight) 
        { 
         _scrollViewer.ScrollToEnd(); 
        } 
       }); 
      timer.Start(); 
     } 
    } 
} 
+31

Ce code vérifie toutes les 2 secondes, toute la journée, s'il y a quelque chose à faire défiler. C'est à la fois plus lent et moins efficace que les solutions pilotées par les événements ci-dessous. – Roland

40

Vous pouvez utiliser ScrollChangedEventArgs.ExtentHeightChange savoir si une ScrollChanged est due à un changement dans le contenu ou à une action de l'utilisateur ... Lorsque le contenu est inchangé, la position ScrollBar définit ou désactive le mode de défilement automatique. Lorsque le contenu a changé, vous pouvez appliquer le défilement automatique.

code

derrière:

private Boolean AutoScroll = true; 

    private void ScrollViewer_ScrollChanged(Object sender, ScrollChangedEventArgs e) 
    { 
     // User scroll event : set or unset auto-scroll mode 
     if (e.ExtentHeightChange == 0) 
     { // Content unchanged : user scroll event 
      if (ScrollViewer.VerticalOffset == ScrollViewer.ScrollableHeight) 
      { // Scroll bar is in bottom 
       // Set auto-scroll mode 
       AutoScroll = true; 
      } 
      else 
      { // Scroll bar isn't in bottom 
       // Unset auto-scroll mode 
       AutoScroll = false; 
      } 
     } 

     // Content scroll event : auto-scroll eventually 
     if (AutoScroll && e.ExtentHeightChange != 0) 
     { // Content changed and auto-scroll mode set 
      // Autoscroll 
      ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight); 
     } 
    } 
+1

Je voulais ce comportement avec un TextBox et il s'est avéré être plus facile d'utiliser ce code et d'intégrer le TextBox dans un ScrollViewer plutôt que d'essayer d'utiliser le défilement intégré de la TextBox. –

+0

Merci, j'ai trouvé cela très utile pour faire défiler automatiquement mon ScrollViewer en fonction du contenu de mon TextBlock. J'ai fait quelques modifications mineures, comme utiliser private bool AutoScroll = true et le mettre dans la méthode. 'private Boolean AutoScroll = true' a provoqué une erreur" expression invalide invalide 'private "". Question, est-ce "style WPF valide"? Ou n'utilise pas la liaison rompre "l'esprit" de WPF? – InvalidBrainException

+0

J'ai essayé de faire une solution plus simple mais j'ai fini par ressembler à celle-ci. Pourtant, je mets la variable AutoScroll dans le hander au lieu de dehors, voir http://stackoverflow.com/questions/25761795/scrollviewer-scrolltoend-only-works-while-debugging – Roland

24

Voici une adaptation de plusieurs sources.

public class ScrollViewerExtensions 
    { 
     public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached("AlwaysScrollToEnd", typeof(bool), typeof(ScrollViewerExtensions), new PropertyMetadata(false, AlwaysScrollToEndChanged)); 
     private static bool _autoScroll; 

     private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e) 
     { 
      ScrollViewer scroll = sender as ScrollViewer; 
      if (scroll != null) 
      { 
       bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue; 
       if (alwaysScrollToEnd) 
       { 
        scroll.ScrollToEnd(); 
        scroll.ScrollChanged += ScrollChanged; 
       } 
       else { scroll.ScrollChanged -= ScrollChanged; } 
      } 
      else { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); } 
     } 

     public static bool GetAlwaysScrollToEnd(ScrollViewer scroll) 
     { 
      if (scroll == null) { throw new ArgumentNullException("scroll"); } 
      return (bool)scroll.GetValue(AlwaysScrollToEndProperty); 
     } 

     public static void SetAlwaysScrollToEnd(ScrollViewer scroll, bool alwaysScrollToEnd) 
     { 
      if (scroll == null) { throw new ArgumentNullException("scroll"); } 
      scroll.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd); 
     } 

     private static void ScrollChanged(object sender, ScrollChangedEventArgs e) 
     { 
      ScrollViewer scroll = sender as ScrollViewer; 
      if (scroll == null) { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); } 

      // User scroll event : set or unset autoscroll mode 
      if (e.ExtentHeightChange == 0) { _autoScroll = scroll.VerticalOffset == scroll.ScrollableHeight; } 

      // Content scroll event : autoscroll eventually 
      if (_autoScroll && e.ExtentHeightChange != 0) { scroll.ScrollToVerticalOffset(scroll.ExtentHeight); } 
     } 
    } 

utiliser dans votre XAML comme ceci:

<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True"> 
    <TextBlock x:Name="Trace"/> 
</ScrollViewer> 
+3

Fonctionne parfaitement. Fait défiler automatiquement lorsque vous faites défiler vers le bas (depuis la configuration initiale ou lorsque restauré par l'utilisateur). Reste fixe lorsque la position de défilement de l'utilisateur est tout sauf le fond. Bel agrégat d'information. +1 également pour les propriétés attachées qui peuvent être ajoutées à ma boîte à outils et réduire le code répétitif. – cod3monk3y

+1

Cela fonctionne très bien, merci! –

+3

C'est excellent. Il est toujours bon d'avoir des propriétés jointes qui fonctionnent correctement. –

3

Voici une méthode que je l'ai utilisé avec de bons résultats. Basé sur deux propriétés de dépendance. Il évite le code derrière et les minuteurs comme indiqué dans l'autre réponse.

public static class ScrollViewerEx 
{ 
    public static readonly DependencyProperty AutoScrollProperty = 
     DependencyProperty.RegisterAttached("AutoScrollToEnd", 
      typeof(bool), typeof(ScrollViewerEx), 
      new PropertyMetadata(false, HookupAutoScrollToEnd)); 

    public static readonly DependencyProperty AutoScrollHandlerProperty = 
     DependencyProperty.RegisterAttached("AutoScrollToEndHandler", 
      typeof(ScrollViewerAutoScrollToEndHandler), typeof(ScrollViewerEx)); 

    private static void HookupAutoScrollToEnd(DependencyObject d, 
      DependencyPropertyChangedEventArgs e) 
    { 
     var scrollViewer = d as ScrollViewer; 
     if (scrollViewer == null) return; 

     SetAutoScrollToEnd(scrollViewer, (bool)e.NewValue); 
    } 

    public static bool GetAutoScrollToEnd(ScrollViewer instance) 
    { 
     return (bool)instance.GetValue(AutoScrollProperty); 
    } 

    public static void SetAutoScrollToEnd(ScrollViewer instance, bool value) 
    { 
     var oldHandler = (ScrollViewerAutoScrollToEndHandler)instance.GetValue(AutoScrollHandlerProperty); 
     if (oldHandler != null) 
     { 
      oldHandler.Dispose(); 
      instance.SetValue(AutoScrollHandlerProperty, null); 
     } 
     instance.SetValue(AutoScrollProperty, value); 
     if (value) 
      instance.SetValue(AutoScrollHandlerProperty, new ScrollViewerAutoScrollToEndHandler(instance)); 
    } 

Ceci utilise un gestionnaire défini comme.

public class ScrollViewerAutoScrollToEndHandler : DependencyObject, IDisposable 
{ 
    readonly ScrollViewer m_scrollViewer; 
    bool m_doScroll = false; 

    public ScrollViewerAutoScrollToEndHandler(ScrollViewer scrollViewer) 
    { 
     if (scrollViewer == null) { throw new ArgumentNullException("scrollViewer"); } 

     m_scrollViewer = scrollViewer; 
     m_scrollViewer.ScrollToEnd(); 
     m_scrollViewer.ScrollChanged += ScrollChanged; 
    } 

    private void ScrollChanged(object sender, ScrollChangedEventArgs e) 
    { 
     // User scroll event : set or unset autoscroll mode 
     if (e.ExtentHeightChange == 0) 
     { m_doScroll = m_scrollViewer.VerticalOffset == m_scrollViewer.ScrollableHeight; } 

     // Content scroll event : autoscroll eventually 
     if (m_doScroll && e.ExtentHeightChange != 0) 
     { m_scrollViewer.ScrollToVerticalOffset(m_scrollViewer.ExtentHeight); } 
    } 

    public void Dispose() 
    { 
     m_scrollViewer.ScrollChanged -= ScrollChanged; 
    } 

Ensuite, il suffit d'utiliser ceci dans XAML comme:

<ScrollViewer VerticalScrollBarVisibility="Auto" 
       local:ScrollViewerEx.AutoScrollToEnd="True"> 
    <TextBlock x:Name="Test test test"/> 
</ScrollViewer> 

Avec local étant une importation d'espace de noms en haut de fichier XAML en question. Cela évite le static bool vu dans d'autres réponses.

2
bool autoScroll = false; 

     if (e.ExtentHeightChange != 0) 
     { 
      if (infoScroll.VerticalOffset == infoScroll.ScrollableHeight - e.ExtentHeightChange) 
      { 
       autoScroll = true; 
      } 
      else 
      { 
       autoScroll = false; 
      } 
     } 
     if (autoScroll) 
     { 
      infoScroll.ScrollToVerticalOffset(infoScroll.ExtentHeight); 
     } 

Вот так вроде-бы привельнее чем у Wallstreet programmeur

+1

В английском языке на этом сайте/English seulement sur ce site. Et vous devez corriger votre code (indentation). –

1

Qu'en est-il en utilisant l'événement "TextChanged" de la zone de texte et la méthode ScrollToEnd()?

private void consolebox_TextChanged(object sender, TextChangedEventArgs e) 
    { 
     this.consolebox.ScrollToEnd(); 
    } 
0

Dans Windows 10, .ScrollToVerticalOffset est obsolète. alors j'utilise ChangeView comme ça.

TextBlock messageBar; 
ScrollViewer messageScroller; 

    private void displayMessage(string message) 
    { 

       messageBar.Text += message + "\n"; 

       double pos = this.messageScroller.ExtentHeight; 
       messageScroller.ChangeView(null, pos, null); 
    } 
0

La réponse précédente a été réécrite pour fonctionner avec la comparaison de nombres à virgule flottante. Sachez que cette solution, bien que simple, empêchera l'utilisateur de défiler dès que le contenu défile vers le bas.

private bool _should_auto_scroll = true; 
private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e) { 
    if (Math.Abs(e.ExtentHeightChange) < float.MinValue) { 
     _should_auto_scroll = Math.Abs(ScrollViewer.VerticalOffset - ScrollViewer.ScrollableHeight) < float.MinValue; 
    } 
    if (_should_auto_scroll && Math.Abs(e.ExtentHeightChange) > float.MinValue) { 
     ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight); 
    } 
} 
Questions connexes