Freigeben über


IScrollInfo in Avalon part III

When we last left the application it had the appearance of something that could scroll, but exceptions were being thrown from unimplemented members left and right. This posting will help out with that.

Let's look at the SetVerticalOffset method as the first case. This method is called when the user drags the thumb on the vertical scrollbar. You are given an offset position, and you do the work to update the scroll position and scroll trhe content. Updating the scroll position is straightforward, but if all you do is change the value of the offset, then the content will not scroll. Because we are implementing IScrollInfo, and we set CanContentScroll, then we are expected to do the work of moving the content around. One way to do this is to use a render transform - this gives us an easy way to offset the rendered position of the content. First we create a render transform and set it on our panel:

        private TranslateTransform _trans = new TranslateTransform();

        public AnnoyingPanel()

        {

            this.RenderTransform = _trans;

        }

This gives us a transform that we can use later on. Now we have enough pieces to implement SetVerticalOffset:

        public void SetVerticalOffset(double offset)

        {

            if (offset < 0 || _viewport.Height >= _extent.Height)

            {

                offset = 0;

            }

            else

            {

                if (offset + _viewport.Height >= _extent.Height)

                {

                    offset = _extent.Height - _viewport.Height;

                }

            }

            _offset.Y = offset;

            if (_owner != null)

                _owner.InvalidateScrollInfo();

   

            _trans.Y = -offset;

        }

Why do we have all of the checking at the start? Won't Avalon only give us valid offsets? Besides the fact that input validation is pretty much always a good idea, we are going to use this method ourselves to help implement other scrolling methods later, and this gives us a centralized place to check the offsets. What checking do we do? First we check if the given offset is less than zero, or if the viewport is larger than the extent. In both of these cases, we need to set the offset to zero. A negative offset makes no sense, and if the viewport is larger than the extent then there is nowhere to scroll and we can set the offset to zero as well.

The other case we need to check is to see if the offset goes too far. The maximum value of the offset that makes sense is the extent minus the viewport height. To see why, imagine the case where the scroll offset is at the maximum allowed value. The bottom of our content will sit at the bottom of the viewport. But the offset is measuring the top of the viewport - so we need to subtract the viewport height from the extent height to get the maximum position.

Compile the app now and you should be able to drag the scroll thumb and scroll your content. Isn't that cool? You can see that we must be really close to the end now. All you need to do is implement the scroll command methods to stop the rest of the exceptions from scroll bar interactions. First we have the LineUp and LineDown methods - we will implement these by changing the scroll position by 1. These methods are called when the scroll up/down buttons are clicked:

        public void LineUp()

        {

            SetVerticalOffset(this.VerticalOffset - 1);

        }

        public void LineDown()

        {

            SetVerticalOffset(this.VerticalOffset + 1);

        }

Now we see why we have the checking before - adding 1 or subtracting 1 will not always give us a valid scroll offset. If you compile now, then the up and down buttons will work as well. How about PageUp/PageDown? Because you are implementing IScrollInfo then you can choose what a page means. For our panel, we will make a full control equal to a page. Since we know that content in our panel is twice the viewport height, we can use this to implement the page methods:

        public void PageUp()

        {

            double childHeight = (_viewport.Height * 2) / this.InternalChildren.Count;

            SetVerticalOffset(this.VerticalOffset - childHeight);

        }

        public void PageDown()

        {

            double childHeight = (_viewport.Height * 2) / this.InternalChildren.Count;

            SetVerticalOffset(this.VerticalOffset + childHeight);

        }

Again, there is scope to refactor this code so that you can have less repetition. A member to calculate the child size would be useful. The last methods that we need for vertical scrolling are the mouse wheel methods. We can implement them by moving in steps of 10:

        public void MouseWheelUp()

        {

            SetVerticalOffset(this.VerticalOffset - 10);

        }

        public void MouseWheelDown()

        {

            SetVerticalOffset(this.VerticalOffset + 10);

        }

And now all interactions with the vertical scrollbar are working, including mouse wheel support. Does this conclude the tutorial? Not quite - we still have not implemented MakeVisible, and we have some subtle bugs with the current implementation that you will see depending on your scroll position when you resize the window. This will be explained in detail in the next installment.

Comments

  • Anonymous
    January 31, 2006
    Hi,

    first of all, thanks for the really good post on IScrollInfo :-) It helped to extend
    knowledge on custom panels but I have questions on the relationship between
    ScrollViewer and its nested Panel.

    Perhaps my sense for the WPF programming model is not that sharp now, but when
    do we need the ScrollOwner reference on ScrollViewer in the child panel? Moreover I'm little confused
    also why we implement properties like Viewport, Offset, a.s.o. on the AnnoyingPanel
    while ScrollViewer also seems to store this data?! I compared both the properties of ScrollViewer and the properties of the AnnoyingPanel with the 'same name'. Is ScrollViewer syncronizing its values or does
    it ask the child panel for it when reading ScrollViewer's properties?

    I apologize the confusing style of asking this questions.

    Thanks
    Mue

  • Anonymous
    February 15, 2006
    Our friends from Max have been
    busy.
    Not only are they cranking away at a beautiful WinFX app, but...

  • Anonymous
    January 05, 2008
    Simply excellent post. I'm pretty sure this series will serve as the de facto writing on IScrollInfo for quite a while. Not much out there now I can imagine how tough it must have been for the author back then. Thumbs up!

  • Anonymous
    June 17, 2008
    PingBack from http://blog.lulutech.com/PermaLink,guid,512d52bd-cde0-4462-96ff-22f504421239.aspx

  • Anonymous
    August 01, 2008
    PingBack from http://rhnatiuk.wordpress.com/2006/12/13/implementing-a-virtualized-panel-in-wpf/

  • Anonymous
    May 31, 2009
    Recently, I was facing performance issue while working with huge data. I need to bind those data inside

  • Anonymous
    June 07, 2009
    PingBack from http://greenteafatburner.info/story.php?id=3754