c# - español - ¿Cómo desplazarse a la parte inferior de un ScrollViewer automáticamente con Xaml y vinculante?
xaml tutorial (6)
Tengo un TextBlock
cuyo contenido está vinculado a una propiedad de cadena de ViewModel. Este TextBlock
tiene un ScrollViewer
alrededor.
Lo que quiero hacer es que cada vez que los registros cambien, el ScrollViewer
se desplace hacia abajo. Idealmente quiero algo como esto:
<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollPosition="{Binding Path=ScrollPosition}">
<TextBlock Text="{Binding Path=Logs}"/>
</ScrollViewer>
¡ No quiero usar Code Behind! La solución que estoy buscando debe ser solo el enlace y / o Xaml.
Aquí hay una pequeña variación.
Esto se desplazará hacia la parte inferior cuando cambien la altura del visor de desplazamiento (ventana gráfica) y la altura del contenido (extensión) del presentador de desplazamiento.
Está basado en la respuesta de Roy T pero no pude comentar, así que lo publiqué como respuesta.
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);
}
}
Desde Geoff''s Blog en ScrollViewer AutoScroll Behavior .
Agrega esta clase:
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);
}
}
}
}
Este código depende de Comportamientos de combinación, que requieren una referencia a System.Windows.Interactivity
. Ver ayuda sobre System.Windows.Interactivity
agregar System.Windows.Interactivity
.
Si instala el paquete MVVM Light NuGet, puede agregar una referencia aquí:
packages/MvvmLightLibs.4.2.30.0/lib/net45/System.Windows.Interactivity.dll
Asegúrese de tener esta propiedad en su encabezado, que apunta a System.Windows.Interactivity.dll
:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Agregue un Comportamiento de Mezcla al ScrollViewer
:
<i:Interaction.Behaviors>
<implementation:AutoScrollBehavior />
</i:Interaction.Behaviors>
Ejemplo:
<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>
Tenemos que agregar una definición para el espacio de nombres, o de lo contrario no sabrá dónde encontrar la clase C # que acabamos de agregar. Agregue esta propiedad a la etiqueta <Window>
. Si está usando ReSharper, automáticamente lo sugerirá.
xmlns:implementation="clr-namespace:MyAttachedBehaviors"
Ahora, si todo va bien, el texto en el cuadro siempre se desplazará hacia abajo.
El ejemplo XAML dado imprimirá los contenidos de la propiedad encuadernada LogText
a la pantalla, lo cual es perfecto para el registro.
Es fácil, ejemplos:
yourContronInside.ScrollOwner.ScrollToEnd ();
yourContronInside.ScrollOwner.ScrollToBottom ();
Estaba usando la respuesta de @Roy T., sin embargo, quería la estipulación adicional de que si retrocedía en el tiempo, pero luego agregaba texto, la vista de desplazamiento debería desplazarse automáticamente a la parte inferior.
Usé esto:
private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
if (e.ExtentHeightChange > 0)
{
scrollViewer.ScrollToEnd();
}
}
en lugar del evento SizeChanged.
Puede crear una propiedad adjunta o un comportamiento para lograr lo que quiere sin usar código detrás. De cualquier forma, aún necesitará escribir algún código.
Aquí hay un ejemplo de uso de propiedad adjunta.
Propiedad adjunta
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();
}
}
}
Enlace Xaml
<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../>
Tendrá que crear una propiedad booleana IsLogsChangedPropertyInViewModel
y establecerla en true cuando se cambie la propiedad de la cadena.
¡Espero que esto ayude! :)
La respuesta actualizada 2017-12-13, ahora usa el evento ScrollChanged y verifica si el tamaño de la extensión cambia. Más confiable y no interfiere con el desplazamiento manual
Sé que esta pregunta es antigua, pero tengo una implementación mejorada:
- Sin dependencias externas
- Solo necesita establecer la propiedad una vez
El código está muy influenciado por las soluciones de Justin XL y 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);
}
}
Uso:
<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace