42 votes

Comment faire défiler automatiquement le bas d'un ScrollViewer avec Xaml et binding ?

J'ai un TextBlock dont le contenu est lié aux données d'une propriété de type chaîne de caractères du ViewModel. Ce site TextBlock a un ScrollViewer enveloppé autour d'elle.

Ce que je veux faire, c'est qu'à chaque fois que les journaux changent, les ScrollViewer défilera vers le bas. Idéalement, je veux quelque chose comme ceci :

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

I no veulent utiliser le Code Behind ! La solution que je recherche devrait être la suivante uniquement et/ou Xaml.

54voto

Justin XL Points 8658

Vous pouvez soit créer une propriété attachée, soit un comportement pour obtenir ce que vous voulez sans utiliser de code derrière. Dans les deux cas, vous devrez toujours écrire du code.

Voici un exemple d'utilisation de la propriété attachée.

Propriété attachée

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

Liaison Xaml

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

Vous devrez créer une propriété booléenne IsLogsChangedPropertyInViewModel et le mettre à true lorsque la propriété string est modifiée.

J'espère que cela vous aidera ! :)

39voto

Roy T. Points 3307

Réponse mise à jour 2017-12-13, utilise maintenant l'événement ScrollChanged et vérifie si la taille de l'étendue change. Plus fiable et n'interfère pas avec le défilement manuel.

Je sais que cette question est ancienne, mais j'ai une mise en œuvre améliorée :

  • Pas de dépendances externes
  • Vous ne devez définir la propriété qu'une seule fois

Le code est fortement influencé par les solutions de Justin XL et de Contango.

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

Utilisation :

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

13voto

Contango Points 7976

Desde Blog de Geoff sur le comportement AutoScroll de ScrollViewer .

Ajoutez cette 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);
            }
        }
    }
}

Ce code dépend de Blend Behaviors, qui requiert une référence à System.Windows.Interactivity . Voir aide pour ajouter System.Windows.Interactivity .

Si vous installez le paquet NuGet MVVM Light, vous pouvez ajouter une référence ici :

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

Assurez-vous que vous avez cette propriété dans votre en-tête, qui pointe vers System.Windows.Interactivity.dll :

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

Ajouter un comportement de mélange dans le ScrollViewer :

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

Exemple :

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

Nous devons ajouter une définition pour l'espace de noms, sinon il ne saura pas où trouver la classe C# que nous venons d'ajouter. Ajoutez cette propriété dans le fichier <Window> étiquette. Si vous utilisez ReSharper, il vous le proposera automatiquement.

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

Maintenant, si tout va bien, le texte dans la boîte défilera toujours vers le bas.

L'exemple XAML donné imprimera le contenu de la propriété liée. LogText à l'écran, ce qui est parfait pour la journalisation.

4voto

C'est facile, des exemples :

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

0voto

Troto Points 25

Voici une légère variation.

Il défilera vers le bas lorsque la hauteur de la visionneuse de défilement (viewport) et la hauteur du contenu de son présentateur de défilement (extent) changeront.

Elle est basée sur la réponse de Roy T. mais comme je n'ai pas pu commenter, je l'ai postée comme une réponse.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X