Как прокручивать в нижней части ScrollViewer автоматически с помощью Xaml и привязки?
У меня есть TextBlock
, контент которого связан с строковым свойством ViewModel. Этот TextBlock
имеет ScrollViewer
, обернутый вокруг него.
То, что я хочу делать, это каждый раз, когда журналы меняются, ScrollViewer
будет прокручиваться в нижнюю часть. В идеале я хочу что-то вроде этого:
<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollPosition="{Binding Path=ScrollPosition}">
<TextBlock Text="{Binding Path=Logs}"/>
</ScrollViewer>
Я не хочу использовать Code Behind! Решение, которое я ищу, должно использовать привязку только и/или Xaml.
Ответы
Ответ 1
Вы можете создать прикрепленное свойство или поведение, чтобы достичь того, чего вы хотите, без использования кода. В любом случае вам все равно придется писать код.
Вот пример использования прикрепленного свойства.
Прикрепленное свойство
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
<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../>
Вам нужно будет создать логическое свойство IsLogsChangedPropertyInViewModel
и установить его в true при изменении свойства строки.
Надеюсь, это поможет!:)
Ответ 2
Отвеченный обновленный 2017-12-13, теперь использует событие ScrollChanged и проверяет, изменяется ли размер экстента. Более надежна и не мешает ручной прокрутке
Я знаю, что этот вопрос старый, но у меня улучшенная реализация:
- Нет внешних зависимостей
- Вам нужно только установить свойство один раз
В код сильно влияют как решения Justin XL, так и 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.SizeChanged += 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);
}
}
Использование:
<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace
Ответ 3
Из Блог Джеффа в стиле прокрутки ScrollViewer.
Добавьте этот класс:
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);
}
}
}
}
Этот код зависит от Blend Behaviors, для которого требуется ссылка на System.Windows.Interactivity
. См. помощь при добавлении System.Windows.Interactivity
.
Если вы устанавливаете пакет MVVM Light NuGet, вы можете добавить ссылку здесь:
packages\MvvmLightLibs.4.2.30.0\lib\net45\System.Windows.Interactivity.dll
Убедитесь, что у вас есть это свойство в заголовке, которое указывает на System.Windows.Interactivity.dll
:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Добавьте Blend Behavior в ScrollViewer
:
<i:Interaction.Behaviors>
<implementation:AutoScrollBehavior />
</i:Interaction.Behaviors>
Пример:
<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>
Мы должны добавить определение для пространства имен, иначе он не будет знать, где найти только что добавленный класс С#. Добавьте это свойство в тег <Window>
. Если вы используете ReSharper, он автоматически предложит вам это.
xmlns:implementation="clr-namespace:MyAttachedBehaviors"
Теперь, если все пойдет хорошо, текст в поле всегда будет прокручиваться вниз.
Приведенный пример XAML напечатает содержимое связанного свойства LogText
на экране, которое идеально подходит для ведения журнала.
Ответ 4
Это легко, примеры:
yourContronInside.ScrollOwner.ScrollToEnd ();
yourContronInside.ScrollOwner.ScrollToBottom ();
Ответ 5
Вот небольшая вариация.
Это будет прокручиваться донизу как при изменении высоты просмотра (видовой области) прокрутки, так и высоты ее прокрутки содержимого (степени) ведущего.
Это основано на ответе Роя Т, но я не смог прокомментировать, поэтому я опубликовал его как ответ.
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);
}
}
Ответ 6
Я использовал @Roy T. ответ, однако я хотел добавить условие, что если вы прокручивались назад во времени, но затем добавил текст, прокрутка должна автоматически прокручивать вниз.
Я использовал это:
private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
if (e.ExtentHeightChange > 0)
{
scrollViewer.ScrollToEnd();
}
}
вместо события SizeChanged.