我的WPF应用程序中有ScrollViewer
,我希望它具有平滑/动画滚动效果,就像 Firefox 一样(如果你知道我在说什么)。< / p>
我试图通过互联网搜索,我发现的唯一一件事就是:
How To Create An Animated ScrollViewer (or ListBox) in WPF
它工作得很好,但我有一个问题 - 它动画滚动效果,但ScrollViewer
的{{1}}直接转到按下的点 - 我希望它是动画的< / p>
如何使Thumb
的{{1}}动画化,或者是否有一个具有相同属性/功能的工作控件?
答案 0 :(得分:45)
在您的示例中,有两个控件继承自ScrollViewer
和ListBox
,动画由SplineDoubleKeyFrame
[MSDN]实现。在我的时间里,我通过附加的依赖属性VerticalOffsetProperty
实现了动画滚动,这允许您直接将偏移滚动条传输到双动画中,如下所示:
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = some value;
verticalAnimation.Duration = new Duration( some duration );
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty)); // Attached dependency property
storyboard.Begin();
例子可以在这里找到:
How to: Animate the Horizontal/VerticalOffset properties of a ScrollViewer
WPF - Animate ListBox.ScrollViewer.HorizontalOffset?
在这种情况下,可以很好地平滑滚动内容和Thumb
。基于此方法,并使用您的示例 [How To Create An Animated ScrollViewer (or ListBox) in WPF],我创建了一个附加行为ScrollAnimationBehavior
,可以应用于ScrollViewer
和ListBox
。
使用示例:
XAML
<Window x:Class="ScrollAnimateBehavior.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:AttachedBehavior="clr-namespace:ScrollAnimateBehavior.AttachedBehaviors"
Title="MainWindow"
WindowStartupLocation="CenterScreen"
Height="350"
Width="525">
<Window.Resources>
<x:Array x:Key="TestArray" Type="{x:Type sys:String}">
<sys:String>TEST 1</sys:String>
<sys:String>TEST 2</sys:String>
<sys:String>TEST 3</sys:String>
<sys:String>TEST 4</sys:String>
<sys:String>TEST 5</sys:String>
<sys:String>TEST 6</sys:String>
<sys:String>TEST 7</sys:String>
<sys:String>TEST 8</sys:String>
<sys:String>TEST 9</sys:String>
<sys:String>TEST 10</sys:String>
</x:Array>
</Window.Resources>
<Grid>
<TextBlock Text="ScrollViewer"
FontFamily="Verdana"
FontSize="14"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="80,80,0,0" />
<ScrollViewer AttachedBehavior:ScrollAnimationBehavior.IsEnabled="True"
AttachedBehavior:ScrollAnimationBehavior.TimeDuration="00:00:00.20"
AttachedBehavior:ScrollAnimationBehavior.PointsToScroll="16"
HorizontalAlignment="Left"
Width="250"
Height="100">
<StackPanel>
<ItemsControl ItemsSource="{StaticResource TestArray}"
FontSize="16" />
</StackPanel>
</ScrollViewer>
<TextBlock Text="ListBox"
FontFamily="Verdana"
FontSize="14"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="0,80,100,0" />
<ListBox AttachedBehavior:ScrollAnimationBehavior.IsEnabled="True"
ItemsSource="{StaticResource TestArray}"
ScrollViewer.CanContentScroll="False"
HorizontalAlignment="Right"
FontSize="16"
Width="250"
Height="100" />
</Grid>
</Window>
Output
IsEnabled
属性负责ScrollViewer
和ListBox
的滚动动画。低于其实施:
public static DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool),
typeof(ScrollAnimationBehavior),
new UIPropertyMetadata(false, OnIsEnabledChanged));
public static void SetIsEnabled(FrameworkElement target, bool value)
{
target.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(FrameworkElement target)
{
return (bool)target.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var target = sender;
if (target != null && target is ScrollViewer)
{
ScrollViewer scroller = target as ScrollViewer;
scroller.Loaded += new RoutedEventHandler(scrollerLoaded);
}
if (target != null && target is ListBox)
{
ListBox listbox = target as ListBox;
listbox.Loaded += new RoutedEventHandler(listboxLoaded);
}
}
在这些Loaded
处理程序中,为PreviewMouseWheel
和PreviewKeyDown
设置了事件处理程序。
Helper(辅助过程)取自示例并提供double
类型的值,该值将传递给过程AnimateScroll()
。这里是动画的神奇之处:
private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue)
{
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = ToValue;
verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty));
storyboard.Begin();
}
<强> Some notes
强>
该示例仅实现了垂直动画,如果您接受此项目,您将意识到自己没有水平动画问题。
选择ListBox
中未转移到下一个元素的当前项目是由于拦截了事件PreviewKeyDown
,所以你必须考虑这个时刻。
此实现完全适合MVVM模式。要在Blend
中使用此行为,您需要继承接口Behavior
。可以找到示例here和here。
Tested on Windows XP, Windows Seven, .NET 4.0.
此link提供了示例项目。
以下是此实施的完整代码:
public static class ScrollAnimationBehavior
{
#region Private ScrollViewer for ListBox
private static ScrollViewer _listBoxScroller = new ScrollViewer();
#endregion
#region VerticalOffset Property
public static DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset",
typeof(double),
typeof(ScrollAnimationBehavior),
new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void SetVerticalOffset(FrameworkElement target, double value)
{
target.SetValue(VerticalOffsetProperty, value);
}
public static double GetVerticalOffset(FrameworkElement target)
{
return (double)target.GetValue(VerticalOffsetProperty);
}
#endregion
#region TimeDuration Property
public static DependencyProperty TimeDurationProperty =
DependencyProperty.RegisterAttached("TimeDuration",
typeof(TimeSpan),
typeof(ScrollAnimationBehavior),
new PropertyMetadata(new TimeSpan(0, 0, 0, 0, 0)));
public static void SetTimeDuration(FrameworkElement target, TimeSpan value)
{
target.SetValue(TimeDurationProperty, value);
}
public static TimeSpan GetTimeDuration(FrameworkElement target)
{
return (TimeSpan)target.GetValue(TimeDurationProperty);
}
#endregion
#region PointsToScroll Property
public static DependencyProperty PointsToScrollProperty =
DependencyProperty.RegisterAttached("PointsToScroll",
typeof(double),
typeof(ScrollAnimationBehavior),
new PropertyMetadata(0.0));
public static void SetPointsToScroll(FrameworkElement target, double value)
{
target.SetValue(PointsToScrollProperty, value);
}
public static double GetPointsToScroll(FrameworkElement target)
{
return (double)target.GetValue(PointsToScrollProperty);
}
#endregion
#region OnVerticalOffset Changed
private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = target as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
#endregion
#region IsEnabled Property
public static DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool),
typeof(ScrollAnimationBehavior),
new UIPropertyMetadata(false, OnIsEnabledChanged));
public static void SetIsEnabled(FrameworkElement target, bool value)
{
target.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(FrameworkElement target)
{
return (bool)target.GetValue(IsEnabledProperty);
}
#endregion
#region OnIsEnabledChanged Changed
private static void OnIsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var target = sender;
if (target != null && target is ScrollViewer)
{
ScrollViewer scroller = target as ScrollViewer;
scroller.Loaded += new RoutedEventHandler(scrollerLoaded);
}
if (target != null && target is ListBox)
{
ListBox listbox = target as ListBox;
listbox.Loaded += new RoutedEventHandler(listboxLoaded);
}
}
#endregion
#region AnimateScroll Helper
private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue)
{
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = ToValue;
verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty));
storyboard.Begin();
}
#endregion
#region NormalizeScrollPos Helper
private static double NormalizeScrollPos(ScrollViewer scroll, double scrollChange, Orientation o)
{
double returnValue = scrollChange;
if (scrollChange < 0)
{
returnValue = 0;
}
if (o == Orientation.Vertical && scrollChange > scroll.ScrollableHeight)
{
returnValue = scroll.ScrollableHeight;
}
else if (o == Orientation.Horizontal && scrollChange > scroll.ScrollableWidth)
{
returnValue = scroll.ScrollableWidth;
}
return returnValue;
}
#endregion
#region UpdateScrollPosition Helper
private static void UpdateScrollPosition(object sender)
{
ListBox listbox = sender as ListBox;
if (listbox != null)
{
double scrollTo = 0;
for (int i = 0; i < (listbox.SelectedIndex); i++)
{
ListBoxItem tempItem = listbox.ItemContainerGenerator.ContainerFromItem(listbox.Items[i]) as ListBoxItem;
if (tempItem != null)
{
scrollTo += tempItem.ActualHeight;
}
}
AnimateScroll(_listBoxScroller, scrollTo);
}
}
#endregion
#region SetEventHandlersForScrollViewer Helper
private static void SetEventHandlersForScrollViewer(ScrollViewer scroller)
{
scroller.PreviewMouseWheel += new MouseWheelEventHandler(ScrollViewerPreviewMouseWheel);
scroller.PreviewKeyDown += new KeyEventHandler(ScrollViewerPreviewKeyDown);
}
#endregion
#region scrollerLoaded Event Handler
private static void scrollerLoaded(object sender, RoutedEventArgs e)
{
ScrollViewer scroller = sender as ScrollViewer;
SetEventHandlersForScrollViewer(scroller);
}
#endregion
#region listboxLoaded Event Handler
private static void listboxLoaded(object sender, RoutedEventArgs e)
{
ListBox listbox = sender as ListBox;
_listBoxScroller = FindVisualChildHelper.GetFirstChildOfType<ScrollViewer>(listbox);
SetEventHandlersForScrollViewer(_listBoxScroller);
SetTimeDuration(_listBoxScroller, new TimeSpan(0, 0, 0, 0, 200));
SetPointsToScroll(_listBoxScroller, 16.0);
listbox.SelectionChanged += new SelectionChangedEventHandler(ListBoxSelectionChanged);
listbox.Loaded += new RoutedEventHandler(ListBoxLoaded);
listbox.LayoutUpdated += new EventHandler(ListBoxLayoutUpdated);
}
#endregion
#region ScrollViewerPreviewMouseWheel Event Handler
private static void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double mouseWheelChange = (double)e.Delta;
ScrollViewer scroller = (ScrollViewer)sender;
double newVOffset = GetVerticalOffset(scroller) - (mouseWheelChange / 3);
if (newVOffset < 0)
{
AnimateScroll(scroller, 0);
}
else if (newVOffset > scroller.ScrollableHeight)
{
AnimateScroll(scroller, scroller.ScrollableHeight);
}
else
{
AnimateScroll(scroller, newVOffset);
}
e.Handled = true;
}
#endregion
#region ScrollViewerPreviewKeyDown Handler
private static void ScrollViewerPreviewKeyDown(object sender, KeyEventArgs e)
{
ScrollViewer scroller = (ScrollViewer)sender;
Key keyPressed = e.Key;
double newVerticalPos = GetVerticalOffset(scroller);
bool isKeyHandled = false;
if (keyPressed == Key.Down)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + GetPointsToScroll(scroller)), Orientation.Vertical);
isKeyHandled = true;
}
else if (keyPressed == Key.PageDown)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + scroller.ViewportHeight), Orientation.Vertical);
isKeyHandled = true;
}
else if (keyPressed == Key.Up)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - GetPointsToScroll(scroller)), Orientation.Vertical);
isKeyHandled = true;
}
else if (keyPressed == Key.PageUp)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - scroller.ViewportHeight), Orientation.Vertical);
isKeyHandled = true;
}
if (newVerticalPos != GetVerticalOffset(scroller))
{
AnimateScroll(scroller, newVerticalPos);
}
e.Handled = isKeyHandled;
}
#endregion
#region ListBox Event Handlers
private static void ListBoxLayoutUpdated(object sender, EventArgs e)
{
UpdateScrollPosition(sender);
}
private static void ListBoxLoaded(object sender, RoutedEventArgs e)
{
UpdateScrollPosition(sender);
}
private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateScrollPosition(sender);
}
#endregion
}
答案 1 :(得分:0)
滚动自定义的最佳示例可以在代码项目的Sacha Barber文章中找到。请参阅此主题的code project article on Friction scrolling 文章。
许多Sacha Barbers WPF代码已经集成到WPF的Github项目中。有关一些非常有用的开源WPF实现,请参阅MahaApps Metro。
答案 2 :(得分:0)
对于那些通过google Anatoliy的代码到达此处的用户,可以使用它们,但是在鼠标滚轮滚动方面存在一些问题。
Scrolling without Fix(请记住,我正在尝试快速滚动到底部)
(自动插入,您可以在Here上找到此应用程序的内容)
让我们看看原因。
#region ScrollViewerPreviewMouseWheel Event Handler
private static void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double mouseWheelChange = (double)e.Delta;
ScrollViewer scroller = (ScrollViewer)sender;
double newVOffset = GetVerticalOffset(scroller) - (mouseWheelChange / 3);
if (newVOffset < 0)
{
AnimateScroll(scroller, 0);
}
else if (newVOffset > scroller.ScrollableHeight)
{
AnimateScroll(scroller, scroller.ScrollableHeight);
}
else
{
AnimateScroll(scroller, newVOffset);
}
e.Handled = true;
}
在此处理程序代码中,您会注意到每次滚动鼠标滚轮都会调用它。因此,当您快速滚动时,动画没有时间来完成,因此您无法从动画中间位置滚动。尝试快速滚动时,这会导致滚动抖动缓慢。
另外,他们的代码在这里:
private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue)
{
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = ToValue;
verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty));
storyboard.Begin();
}
具有不需要的情节提要实现,可以将其删除以使滚动动画可中断,这是我们需要的,以便使快速滚动变得平滑。
我们将在其代码的顶部添加一个新变量。
public static class ScrollAnimationBehavior
{
public static double intendedLocation = 0;
...
我们需要保存动画的预期位置,以便再次调用滚动事件时,可以在开始下一个动画调用之前立即跳转到该位置。
现在,我们还需要更改其他内容。当用户使用击键事件之一(向上翻页或向下翻页)或使用鼠标手动移动滚动条时,就需要更新意向位置。
因此,我们需要添加另一个事件来处理scrollviewer上的鼠标左键,并且在抬起鼠标(放置在预期位置)时,我们可以更改预期位置,以便滚轮获得更新的位置
private static void SetEventHandlersForScrollViewer(ScrollViewer scroller)
{
scroller.PreviewMouseWheel += new MouseWheelEventHandler(ScrollViewerPreviewMouseWheel);
scroller.PreviewKeyDown += new KeyEventHandler(ScrollViewerPreviewKeyDown);
scroller.PreviewMouseLeftButtonUp += Scroller_PreviewMouseLeftButtonUp;
}
private static void Scroller_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
intendedLocation = ((ScrollViewer)sender).VerticalOffset;
}
尽管如此,我们仍然需要更新上一页和下一页区域。
private static void ScrollViewerPreviewKeyDown(object sender, KeyEventArgs e)
{
ScrollViewer scroller = (ScrollViewer)sender;
Key keyPressed = e.Key;
double newVerticalPos = GetVerticalOffset(scroller);
bool isKeyHandled = false;
if (keyPressed == Key.Down)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + GetPointsToScroll(scroller)), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.PageDown)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + scroller.ViewportHeight), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.Up)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - GetPointsToScroll(scroller)), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.PageUp)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - scroller.ViewportHeight), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
if (newVerticalPos != GetVerticalOffset(scroller))
{
intendedLocation = newVerticalPos;
AnimateScroll(scroller, newVerticalPos);
}
e.Handled = isKeyHandled;
}
现在我们已经处理了非鼠标滚轮事件以更新预期的位置,让我们修复鼠标滚轮事件。
private static void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double mouseWheelChange = (double)e.Delta;
ScrollViewer scroller = (ScrollViewer)sender;
double newVOffset = intendedLocation - (mouseWheelChange * 2);
//Incase we got hit by the mouse again. jump to the offset.
scroller.ScrollToVerticalOffset(intendedLocation);
if (newVOffset < 0)
{
newVOffset = 0;
}
if (newVOffset > scroller.ScrollableHeight)
{
newVOffset = scroller.ScrollableHeight;
}
AnimateScroll(scroller, newVOffset);
intendedLocation = newVOffset;
e.Handled = true;
}
所以更改如下
已将newVOffset更改为可从指定位置和mouseWheelChange起作用。
当newVOffset超出或低于可接受范围时清理。
我们跳到了预期位置,该位置由上一个滚轮事件及其想要到达的位置创建。
如果要更改滚动的“速度”,只需更改
double newVOffset = intendedLocation - (mouseWheelChange * 2);
您可以将修改器的时间从2更改为5,以使速度更快,或1更改为慢速。
现在处理所有事件,让动画自行取消,所以这一切都有重点。
private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue)
{
scrollViewer.BeginAnimation(VerticalOffsetProperty, null);
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = ToValue;
verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer));
scrollViewer.BeginAnimation(VerticalOffsetProperty, verticalAnimation);
}
所以我们在这里所做的是删除情节提要,并取消任何现有动画,以便我们可以从头开始制作一个新的动画。
下面是完整代码(按原样提供),以防您懒得像我只是在复制它一样更改它。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Input;
using ScrollAnimateBehavior.Helpers;
namespace ScrollAnimateBehavior.AttachedBehaviors
{
public static class ScrollAnimationBehavior
{
public static double intendedLocation = 0;
#region Private ScrollViewer for ListBox
private static ScrollViewer _listBoxScroller = new ScrollViewer();
#endregion
#region VerticalOffset Property
public static DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset",
typeof(double),
typeof(ScrollAnimationBehavior),
new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void SetVerticalOffset(FrameworkElement target, double value)
{
target.SetValue(VerticalOffsetProperty, value);
}
public static double GetVerticalOffset(FrameworkElement target)
{
return (double)target.GetValue(VerticalOffsetProperty);
}
#endregion
#region TimeDuration Property
public static DependencyProperty TimeDurationProperty =
DependencyProperty.RegisterAttached("TimeDuration",
typeof(TimeSpan),
typeof(ScrollAnimationBehavior),
new PropertyMetadata(new TimeSpan(0, 0, 0, 0, 0)));
public static void SetTimeDuration(FrameworkElement target, TimeSpan value)
{
target.SetValue(TimeDurationProperty, value);
}
public static TimeSpan GetTimeDuration(FrameworkElement target)
{
return (TimeSpan)target.GetValue(TimeDurationProperty);
}
#endregion
#region PointsToScroll Property
public static DependencyProperty PointsToScrollProperty =
DependencyProperty.RegisterAttached("PointsToScroll",
typeof(double),
typeof(ScrollAnimationBehavior),
new PropertyMetadata(0.0));
public static void SetPointsToScroll(FrameworkElement target, double value)
{
target.SetValue(PointsToScrollProperty, value);
}
public static double GetPointsToScroll(FrameworkElement target)
{
return (double)target.GetValue(PointsToScrollProperty);
}
#endregion
#region OnVerticalOffset Changed
private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = target as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
#endregion
#region IsEnabled Property
public static DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool),
typeof(ScrollAnimationBehavior),
new UIPropertyMetadata(false, OnIsEnabledChanged));
public static void SetIsEnabled(FrameworkElement target, bool value)
{
target.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(FrameworkElement target)
{
return (bool)target.GetValue(IsEnabledProperty);
}
#endregion
#region OnIsEnabledChanged Changed
private static void OnIsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var target = sender;
if (target != null && target is ScrollViewer)
{
ScrollViewer scroller = target as ScrollViewer;
scroller.Loaded += new RoutedEventHandler(scrollerLoaded);
}
if (target != null && target is ListBox)
{
ListBox listbox = target as ListBox;
listbox.Loaded += new RoutedEventHandler(listboxLoaded);
}
}
#endregion
#region AnimateScroll Helper
private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue)
{
scrollViewer.BeginAnimation(VerticalOffsetProperty, null);
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = ToValue;
verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer));
scrollViewer.BeginAnimation(VerticalOffsetProperty, verticalAnimation);
}
#endregion
#region NormalizeScrollPos Helper
private static double NormalizeScrollPos(ScrollViewer scroll, double scrollChange, Orientation o)
{
double returnValue = scrollChange;
if (scrollChange < 0)
{
returnValue = 0;
}
if (o == Orientation.Vertical && scrollChange > scroll.ScrollableHeight)
{
returnValue = scroll.ScrollableHeight;
}
else if (o == Orientation.Horizontal && scrollChange > scroll.ScrollableWidth)
{
returnValue = scroll.ScrollableWidth;
}
return returnValue;
}
#endregion
#region UpdateScrollPosition Helper
private static void UpdateScrollPosition(object sender)
{
ListBox listbox = sender as ListBox;
if (listbox != null)
{
double scrollTo = 0;
for (int i = 0; i < (listbox.SelectedIndex); i++)
{
ListBoxItem tempItem = listbox.ItemContainerGenerator.ContainerFromItem(listbox.Items[i]) as ListBoxItem;
if (tempItem != null)
{
scrollTo += tempItem.ActualHeight;
}
}
AnimateScroll(_listBoxScroller, scrollTo);
}
}
#endregion
#region SetEventHandlersForScrollViewer Helper
private static void SetEventHandlersForScrollViewer(ScrollViewer scroller)
{
scroller.PreviewMouseWheel += new MouseWheelEventHandler(ScrollViewerPreviewMouseWheel);
scroller.PreviewKeyDown += new KeyEventHandler(ScrollViewerPreviewKeyDown);
scroller.PreviewMouseLeftButtonUp += Scroller_PreviewMouseLeftButtonUp;
}
private static void Scroller_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
intendedLocation = ((ScrollViewer)sender).VerticalOffset;
}
#endregion
#region scrollerLoaded Event Handler
private static void scrollerLoaded(object sender, RoutedEventArgs e)
{
ScrollViewer scroller = sender as ScrollViewer;
SetEventHandlersForScrollViewer(scroller);
}
#endregion
#region listboxLoaded Event Handler
private static void listboxLoaded(object sender, RoutedEventArgs e)
{
ListBox listbox = sender as ListBox;
_listBoxScroller = FindVisualChildHelper.GetFirstChildOfType<ScrollViewer>(listbox);
SetEventHandlersForScrollViewer(_listBoxScroller);
SetTimeDuration(_listBoxScroller, new TimeSpan(0, 0, 0, 0, 200));
SetPointsToScroll(_listBoxScroller, 16.0);
listbox.SelectionChanged += new SelectionChangedEventHandler(ListBoxSelectionChanged);
listbox.Loaded += new RoutedEventHandler(ListBoxLoaded);
listbox.LayoutUpdated += new EventHandler(ListBoxLayoutUpdated);
}
#endregion
#region ScrollViewerPreviewMouseWheel Event Handler
private static void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double mouseWheelChange = (double)e.Delta;
ScrollViewer scroller = (ScrollViewer)sender;
double newVOffset = intendedLocation - (mouseWheelChange * 2);
//We got hit by the mouse again. jump to the offset.
scroller.ScrollToVerticalOffset(intendedLocation);
if (newVOffset < 0)
{
newVOffset = 0;
}
if (newVOffset > scroller.ScrollableHeight)
{
newVOffset = scroller.ScrollableHeight;
}
AnimateScroll(scroller, newVOffset);
intendedLocation = newVOffset;
e.Handled = true;
}
#endregion
#region ScrollViewerPreviewKeyDown Handler
private static void ScrollViewerPreviewKeyDown(object sender, KeyEventArgs e)
{
ScrollViewer scroller = (ScrollViewer)sender;
Key keyPressed = e.Key;
double newVerticalPos = GetVerticalOffset(scroller);
bool isKeyHandled = false;
if (keyPressed == Key.Down)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + GetPointsToScroll(scroller)), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.PageDown)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + scroller.ViewportHeight), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.Up)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - GetPointsToScroll(scroller)), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
else if (keyPressed == Key.PageUp)
{
newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - scroller.ViewportHeight), Orientation.Vertical);
intendedLocation = newVerticalPos;
isKeyHandled = true;
}
if (newVerticalPos != GetVerticalOffset(scroller))
{
intendedLocation = newVerticalPos;
AnimateScroll(scroller, newVerticalPos);
}
e.Handled = isKeyHandled;
}
#endregion
#region ListBox Event Handlers
private static void ListBoxLayoutUpdated(object sender, EventArgs e)
{
UpdateScrollPosition(sender);
}
private static void ListBoxLoaded(object sender, RoutedEventArgs e)
{
UpdateScrollPosition(sender);
}
private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateScrollPosition(sender);
}
#endregion
}
}