Je voudrais créer le comportement suivant dans une ScrollViewer
qui enveloppe ContentControl
:
Lorsque la hauteur ContentControl
augmente, la ScrollViewer
devrait défiler automatiquement jusqu'à 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 du VS par exemple.
Le problème est de savoir quand un défilement est survenu à cause du défilement de l'utilisateur et quand il s'est produit parce que la taille du contenu a changé. J'ai essayé de jouer avec la ScrollChangedEventArgs
de ScrollChangedEvent
, mais je n'ai pas réussi à la faire fonctionner.
Idéalement, je ne souhaite pas gérer tous les événements de souris et de clavier possibles.
Ce code défilera automatiquement jusqu'à la fin lorsque le contenu s'agrandira s'il avait déjà fait défiler le contenu.
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();
}
}
}
Vous pouvez utiliser ScrollChangedEventArgs.ExtentHeightChange pour savoir si ScrollChanged est dû à une modification du contenu ou à une action de l'utilisateur .... Lorsque le contenu n'est pas modifié, 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);
}
}
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); }
}
}
Utilisez-le dans votre XAML comme ceci:
<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True">
<TextBlock x:Name="Trace"/>
</ScrollViewer>
Voici une méthode que j'ai utilisée avec de bons résultats. Basé sur deux propriétés de dépendance. Cela évite le code en retard et les minuteries, 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, utilisez simplement ceci en XAML comme:
<ScrollViewer VerticalScrollBarVisibility="Auto"
local:ScrollViewerEx.AutoScrollToEnd="True">
<TextBlock x:Name="Test test test"/>
</ScrollViewer>
local
étant une importation d’espace de noms en haut du fichier XAML en question. Cela évite le static bool
vu dans d'autres réponses.
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);
}
Cliquez ici pour en savoir plus sur le programmeur Wallstreet
Pourquoi ne pas utiliser l'événement "TextChanged" de TextBox et la méthode ScrollToEnd ()?
private void consolebox_TextChanged(object sender, TextChangedEventArgs e)
{
this.consolebox.ScrollToEnd();
}
La réponse précédente a été réécrite pour fonctionner avec une comparaison en virgule flottante. Sachez que cette solution, bien que simple, empêchera l'utilisateur de faire défiler le contenu dès que le contenu sera défilé.
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);
}
}
Dans Windows 10, .ScrollToVerticalOffset est obsolète. donc 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);
}