Frage So scrollen Sie automatisch ScrollViewer - nur wenn der Benutzer die Bildlaufposition nicht geändert hat


Ich möchte das folgende Verhalten in einem erstellen ScrollViewer das wickelt sich ein ContentControl:
Wenn das ContentControl Höhe wächst, die ScrollViewer sollte automatisch bis zum Ende scrollen. Dies ist einfach zu verwenden ScrollViewer.ScrollToEnd().
Wenn der Benutzer jedoch die Bildlaufleiste verwendet, sollte das automatische Scrollen nicht mehr vorkommen. Dies ist ähnlich wie im VS-Ausgabefenster.

Das Problem besteht darin, zu wissen, wann ein Bildlauf aufgrund des Benutzerscrollens und wann es passiert ist, weil sich die Inhaltsgröße geändert hat. Ich habe versucht mit dem zu spielen ScrollChangedEventArgsvon ScrollChangedEvent, konnte es aber nicht zur Arbeit bringen.

Im Idealfall möchte ich nicht alle möglichen Maus- und Tastaturereignisse behandeln.


26
2018-06-06 15:48


Ursprung


Antworten:


Dieser Code scrollt automatisch bis zum Ende, wenn der Inhalt wächst, wenn er zuvor vollständig durchgeblättert wurde.

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

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

7
2018-06-07 22:44



Sie können ScrollChangedEventArgs.ExtentHeightChange verwenden, um zu ermitteln, ob ein ScrollChanged aufgrund einer Änderung des Inhalts oder einer Benutzeraktion aufgetreten ist. Wenn der Inhalt unverändert ist, wird durch die ScrollBar-Position der automatische Bildlaufmodus festgelegt oder aufgehoben. Wenn sich der Inhalt geändert hat, können Sie das automatische Scrollen anwenden.

Code dahinter:

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

47
2017-10-11 09:54



Hier ist eine Anpassung aus mehreren Quellen.

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

Verwenden Sie es in Ihrem XAML wie folgt:

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

26
2018-02-12 20:07



Hier ist eine Methode, die ich mit guten Ergebnissen verwendet habe. Basierend auf zwei Abhängigkeitseigenschaften. Es vermeidet Code hinter und Timer wie in der anderen Antwort gezeigt.

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

Dies verwendet einen Handler, der als definiert ist.

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

Dann benutze das einfach in XAML als:

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

Mit local Ein Namespace-Import am Anfang der betreffenden XAML-Datei. Dies vermeidet die static bool in anderen Antworten gesehen.


3
2018-05-09 09:55



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

Hier finden Sie weitere Informationen zu Wallstreet Programmer


2
2018-01-05 16:47



Was ist mit der Verwendung des "TextChanged" -Ereignisses der TextBox- und der ScrollToEnd () -Methode?

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

1
2017-07-04 07:20



In Windows 10 ist .ScrollToVerticalOffset veraltet. also benutze ich Ansicht ändern so was.

TextBlock messageBar;
ScrollViewer messageScroller; 

    private void displayMessage(string message)
    {

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

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

0
2017-07-23 16:02



Vorherige Antwort wurde neu geschrieben, um mit Gleitkommavergleich zu arbeiten. Seien Sie sich bewusst, dass diese Lösung, obwohl einfach, den Benutzer daran hindern wird, zu scrollen, sobald der Inhalt nach unten gescrollt wird.

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

0
2018-03-13 18:05