WPFアプリにScrollViewer
があり、Firefoxのようにスムーズ/アニメーション化されたスクロール効果を持たせたい私が話していることを知っています)。
私はインターネットで検索しようとしましたが、私が見つけた唯一のものはこれです:
WPFでアニメーションScrollViewer(またはListBox)を作成する方法
かなりうまく機能しますが、問題が1つあります。スクロール効果はアニメーション化されますが、ScrollViewer
のThumb
は押されたポイントに直接移動します。
ScrollViewer
のThumb
を同様にアニメーション化するにはどうすればよいですか。それとも、必要な同じプロパティ/機能を持つ機能するコントロールがありますか?
あなたの例では、ScrollViewer
とListBox
から継承された2つのコントロールがあり、アニメーションは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();
_
例はここにあります:
方法:ScrollViewerのHorizontal/VerticalOffsetプロパティをアニメーション化する
WPF-ListBox.ScrollViewer.HorizontalOffsetをアニメートしますか?
この場合、コンテンツとThumb
のスムーズなスクロールがうまく機能します。このアプローチに基づいて、例を使用して [WPFでアニメーションScrollViewer(またはListBox)を作成する方法] 、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
のイベントハンドラーが設定されています。
ヘルパー(補助プロシージャ)は例から取られ、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
}
_
Google Anatoliyのコードからここにアクセスする人には機能しますが、特にマウスホイールのスクロールにいくつかの問題があります。
修正なしのスクロール (最下部まで高速スクロールしようとしていることに留意してください)
(自己プラグイン、このアプリケーションが何であるかを知ることができます ここ )
理由を見てみましょう。
#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;
...
アニメーションの目的の場所を保存する必要があります。そうすると、scrollerイベントを再度呼び出した場合、次のアニメーション呼び出しを開始する前にその場所にすぐにジャンプできます。
今、私たちが変えなければならない何かがあります。ユーザーがキーダウンイベント(Page UpまたはPage Down)のいずれかを使用するか、マウスでスクロールバーを手動で移動する場合、意図したLocationを更新する必要があります。
したがって、スクロールビューアーでマウスの左ボタンを処理する別のイベントを追加する必要があります。マウスを持ち上げると(目的の場所に置く)、目的の場所を変更して、スクロールホイールが更新された位置を取得できるようにします。
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
}
}
スクロールのカスタマイズの最良の例は、Code Projectの Sacha Barber の記事にあります。これを参照してください 摩擦スクロールに関するプロジェクトの記事をコード化する トピックに関する記事。
多くのSacha Barbers WPFコードがWPFのGithubプロジェクトに統合されました。非常に便利なオープンソースのWPF実装については、 MahaApps Metro を参照してください。