web-dev-qa-db-ger.com

Wie scrollen Sie automatisch mit Xaml und der Bindung zum unteren Rand eines ScrollViewers?

Ich habe eine TextBlock, deren Inhalt Daten an eine String-Eigenschaft des ViewModel gebunden ist. Um diese TextBlock ist eine ScrollViewer gewickelt.

Ich möchte jedes Mal, wenn sich die Protokolle ändern, die ScrollViewer nach unten scrollen. Im Idealfall möchte ich so etwas:

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

Ich nicht möchte Code Behind verwenden! Die Lösung, die ich suche, sollte nur Bindung und/oder Xaml verwenden.

35
JiBéDoublevé

Sie können entweder eine angefügte Eigenschaft oder ein Verhalten erstellen, um das zu erreichen, was Sie möchten, ohne Code zu verwenden. In jedem Fall müssen Sie noch Code schreiben.

Hier ist ein Beispiel für die Verwendung der angefügten Eigenschaft.

Angebrachtes Eigentum

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

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

Sie müssen eine boolesche Eigenschaft IsLogsChangedPropertyInViewModel erstellen und diese auf true setzen, wenn die Zeichenfolgeeigenschaft geändert wird.

Hoffe das hilft! :)

44
Justin XL

Antwort aktualisiert 2017-12-13, verwendet jetzt das ScrollChanged-Ereignis und prüft, ob sich die Größe des Bereichs ändert. Zuverlässiger und stört nicht beim manuellen Scrollen

Ich weiß, dass diese Frage alt ist, aber ich habe eine verbesserte Implementierung erhalten:

  • Keine externen Abhängigkeiten
  • Sie müssen die Eigenschaft nur einmal festlegen

Der Code ist stark von den Lösungen von Justin XL und Contango beeinflusst

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

Verwendungszweck:

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

Aus Geoffs Blog über ScrollViewer AutoScroll-Verhalten .

Diese Klasse hinzufügen:

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

Dieser Code hängt von Blend-Verhalten ab, für die ein Verweis auf System.Windows.Interactivity erforderlich ist. Siehe Hilfe zum Hinzufügen von System.Windows.Interactivity .

Wenn Sie das MVVM Light NuGet-Paket installieren, können Sie hier eine Referenz hinzufügen:

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

Stellen Sie sicher, dass sich diese Eigenschaft in Ihrer Kopfzeile befindet, die auf System.Windows.Interactivity.dll zeigt:

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

Fügen Sie der Variablen ScrollViewer ein Mischverhalten hinzu:

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

Beispiel:

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

Wir müssen eine Definition für den Namespace hinzufügen, sonst weiß er nicht, wo sich die gerade hinzugefügte C # -Klasse befindet. Fügen Sie diese Eigenschaft dem <Window>-Tag hinzu. Wenn Sie ReSharper verwenden, wird dies automatisch für Sie vorgeschlagen.

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

Wenn jetzt alles gut geht, wird der Text in der Box immer nach unten gescrollt.

Mit dem angegebenen Beispiel-XAML wird der Inhalt der gebundenen Eigenschaft LogText auf dem Bildschirm gedruckt. Dies ist ideal für die Protokollierung.

11
Contango

Es ist einfach, Beispiele:

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

Hier ist eine leichte Abweichung.

Dies wird sowohl nach unten als auch nach unten scrollen, wenn sich die Höhe des Bildbetrachters (Ansichtsbereich) und die Höhe des Inhalts des Bildlaufdarstellers (Ausdehnung) ändern. 

Es basiert auf der Antwort von Roy T., aber ich konnte nicht kommentieren, deshalb habe ich als Antwort gepostet.

    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