Click here to Skip to main content
15,881,852 members
Articles / Desktop Programming / WPF

WPF scrollviewer with Inertia

Rate me:
Please Sign up or sign in to vote.
3.00/5 (4 votes)
10 Sep 2018MIT3 min read 8.7K   3   4
Implementation of WPF scrollviewer with inertia

1. First Look at the Effect

Image 1

2. Principle

Although the effect is very simple, some of the information on the Internet involves a considerable amount of code, and the effect is not very ideal, scrolling without a smooth feeling. I provide a total of no more than 130 lines of source code, can achieve the effect of the above.

Essentially, we just have to take over the scrolling logic of ScrollViewer and replace that logic with inertia, so how do we take over? The key here is Shielding ScrollViewer's mouse wheel event first:

C#
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
    e.Handled = true;
}

So that ScrollViewer won't respond to mouse wheel event, we're going to be here to make a story. First, we add a property IsEnableInertia to this ScrollViewer to control whether to use inertia. Different strokes for different folks. Don't try to force everyone to use inertia, so the wheel response method becomes:

C#
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
    if (!IsEnableInertia)
    {
        base.OnMouseWheel(e);
        return;
    }
    e.Handled = true;
}

You can use ScrollViewer.ScrollToVerticalOffset to control the vertical scrolling of the ScrollViewer, as well as horizontally. Why not use VerticalOffset? Because when VerticalOffset was registered, it stated that it was read-only:

C#
private static readonly DependencyPropertyKey VerticalOffsetPropertyKey = 
       DependencyProperty.RegisterReadOnly(nameof (VerticalOffset), typeof (double), 
       typeof (ScrollViewer), (PropertyMetadata) new FrameworkPropertyMetadata((object) 0.0));

public static readonly DependencyProperty VerticalOffsetProperty = 
       ScrollViewer.VerticalOffsetPropertyKey.DependencyProperty;

Well, the next step is how to achieve inertial motion in the wheel response method, that is, a declaration motion. People who are familiar with animation quickly know that the WPF animation is ideal for doing inertia, and its description is as follows:

Image 2

In the figure, the horizontal axis indicates time and the vertical axis indicates the distance of motion. Obviously, the middle EaseOut pattern is what we want. We can define a property CurrentVerticalOffset on which we will animate and call ScrollViewer.ScrollToVerticalOffset in its value callback function to update the scroll position of ScrollViewer. Of course, we also need a private field _totalVerticalOffset, which is used to hold the ScrollViewer scrolling target position, and the scroll wheel scrolls down one unit and subtracts the value of e.Delta once, where ‘e’ is the parameter that the wheel response method passes in. Each time you assign it a value, you can execute the animation on the CurrentVerticalOffsetBeginAnimation(CurrentVerticalOffsetProperty, animation). It is important to note that when a dependent property is changed by animation, the assignment does not take effect because the timeline by default keeps an animation at the end of the activity period until the end of its parent's active and retention period. If you want to manually change the value of dependent properties after the animation, you need to set FillBehavior to Stop. But once the animation is finished, the dependent property reverts to the initial value, so subscribe to a Completed event for the animation, giving the CurrentVerticalOffset a target value in the event response method, that is, _totalVerticalOffset.

Finally, there's a conflict that you can't animate when you manually drag a slider or when you use a context menu to change the position of a scroll bar, because OnMouseWheel is not triggered at this time. That's all right, that's exactly what we want, but it's a problem if we trigger OnMouseWheel again, because we don't assign values to CurrentVerticalOffset and _totalVerticalOffset when manually triggering scrolling(CurrentVerticalOffset and _totalVerticalOffset only assign values in OnMouseWheel), So before you scroll with animations, you need to know if you need to update them first. How do you judge them? We can maintain the state with a private field _isRunning. Each time the animation starts, it is assigned a true, end, then the false is assigned. So, when it's false, it means that before calling OnMouseWheel, the animation is over, the user may have manually changed the scroll bar position (or not, but that doesn't matter), so update the values for the two brothers.

Because most of the usual inertial scrolling is vertical, I do not write horizontal logic, but it is also easy to expand, interested friends can download the source code to modify.

This article was originally posted at https://github.com/NaBian/HandyControl

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseGood Job Pin
Jeff Bowman29-Apr-19 7:06
professionalJeff Bowman29-Apr-19 7:06 
QuestionPlease update Pin
tbayart10-Sep-18 2:20
professionaltbayart10-Sep-18 2:20 
AnswerRe: Please update Pin
NaBian10-Sep-18 20:06
NaBian10-Sep-18 20:06 
GeneralRe: Please update Pin
tbayart10-Sep-18 23:57
professionaltbayart10-Sep-18 23:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.