web-dev-qa-db-fra.com

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

J'ai une TextBlock dont le contenu est lié à une propriété de chaîne du ViewModel. Cette TextBlock est entourée d'une ScrollViewer.

Ce que je veux faire est que chaque fois que les journaux changent, la ScrollViewer défilera vers le bas. Idéalement, je veux quelque chose comme ça:

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

Je _ {non} _ veux utiliser Code Behind! La solution que je recherche devrait utiliser uniquement binding et/ou Xaml.

35
JiBéDoublevé

Vous pouvez créer une propriété attachée ou un comportement pour obtenir ce que vous voulez sans utiliser de code derrière. De toute façon, vous aurez toujours besoin d'é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();
        }
    }
}

Xaml Binding

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

Vous devrez créer une propriété booléenne IsLogsChangedPropertyInViewModel et la définir sur true lorsque la propriété string sera modifiée.

J'espère que cela t'aides! :)

44
Justin XL

Réponse mise à jour le 13/12/2017, 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 implémentation améliorée:

  • Aucune dépendance externe
  • Il vous suffit de définir la propriété une 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);
    }
}

Usage:

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

De Blog de Geoff sur le comportement AutoScroll de ScrollViewer .

Ajouter 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 des comportements de mélange, qui nécessitent une référence à System.Windows.Interactivity. Voir Aide sur l'ajout de System.Windows.Interactivity .

Si vous installez le package MVVM Light NuGet, 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 sur System.Windows.Interactivity.dll:

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

Ajoutez un comportement de fusion dans la 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é à la balise <Window>. Si vous utilisez ReSharper, il vous le suggérera automatiquement.

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

Maintenant, si tout se passe bien, le texte dans la zone 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.

11
Contango

C'est facile, des exemples:

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

Voici une légère variation.

Cela fera défiler vers le bas à la fois lorsque la hauteur de la visionneuse de défilement (fenêtre d'affichage) et la hauteur de son contenu (étendue) sont affichées. 

C'est basé sur la réponse de Roy T mais je n'ai pas pu commenter, alors j'ai posté comme 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);
        }
    }
0
Troto