Controlling and Viewing the ScrollBar In Silverlight DataGrid

No.of Views1416
Bookmarked0 times
Downloads 
Votes0
By  Dhananjay Kumar   On  16 Feb 2010 00:02:56
Tag : Silver Light and XAML , Custom Controls
If you take a look at the Silverlight DataGrid you'll see that you can't control scrolling by default. This could be a hurdle if you're working with data driven applications. What if you want to preserve the scroll position after you reload the DataGrid
emailbookmarkadd commentsprint

Images in this article missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at info@codegain.com

 

Introduction

If you take a look at the Silverlight DataGrid you'll see that you can't control scrolling by default. This could be a hurdle if you're working with data driven applications. What if you want to preserve the scroll position after you reload the DataGrid?

In this article we're going to do 2 things in regard to scrolling:

  1. Create extension methods to control the DataGrid scrolling.
  2. Create a custom DataGrid for advanced scenarios.

Scrollbars on the DataGrid

Before we get started here is a quick overview of the DataGrid with scrollbars. Each DataGrid could have up to 2 scrollbars (1 horizontal and 1 vertical). And the button you can drag around is called a thumb (from System.Windows.Controls.Primitives.Thumb).

 

Creating the DataGridScrollExtensions class

The class we'll create should provide the following functionalities:

  • Give us access to both the horizontal and vertical scrollbars
  • Inform us about the position (and changes) of the scrollbar thumbs
  • Move the scrollbar thumbs

This is how the class looks like:

 

Finding the Scrollbars

The first thing to do is make sure we can access the scrollbars. This is where we'll use the recursive method GetScrollbars. Using the VisualTreeHelper we can drill down through a DependencyObject (in our case the DataGrid) until we find the Scrollbar. The names you can use to find the right scrollbars are VerticalScrollbar and HorizontalScrollbar (case sensitive!).

        private static ScrollBar GetScrollbar(this DependencyObject dep, string name)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
            {
                var child = VisualTreeHelper.GetChild(dep, i);
                if (child != null && child is ScrollBar && ((ScrollBar)child).Name == name)
                    return child as ScrollBar;
                else                {
                    ScrollBar sub = GetScrollbar(child, name);
                    if (sub != null)
                        return sub;
                }
            }
            return null;
        }  

Now that we have access to the scrollbars we can get to all the information we need. You'll probably want to look at the Maximum and Value properties and also the Scroll event.

Moving the Scrollbars

Moving the scrollbars around is a little trickier. I bet you're thinking the same as I was thinking. Just change the Value property of a scrollbar. Well, I suggest you try that. You'll see that the Thumb will move to the new value of the scrollbar, but the data won't follow. Let's take a look at the DataGrid using Reflector:

        public override void OnApplyTemplate()
        {
            ...            this._hScrollBar = base.GetTemplateChild("HorizontalScrollbar"as ScrollBar;
            if (this._hScrollBar != null)
            {
                this._hScrollBar.IsTabStop = false;
                this._hScrollBar.Maximum = 0.0;
                this._hScrollBar.Orientation = Orientation.Horizontal;
                this._hScrollBar.Visibility = Visibility.Collapsed;
                this._hScrollBar.Scroll += new ScrollEventHandler(this.HorizontalScrollBar_Scroll);
            }
            ...            this._vScrollBar = base.GetTemplateChild("VerticalScrollbar"as ScrollBar;
            if (this._vScrollBar != null)
            {
                this._vScrollBar.IsTabStop = false;
                this._vScrollBar.Maximum = 0.0;
                this._vScrollBar.Orientation = Orientation.Vertical;
                this._vScrollBar.Visibility = Visibility.Collapsed;
                this._vScrollBar.Scroll += new ScrollEventHandler(this.VerticalScrollBar_Scroll);
            }
            ...        }

As you can see the DataGrid subscribes to each ScrollBar's Scroll event. When this event is raised the DataGrid will move the data around. But if you just change the Value of a ScrollBar, the Scroll event won't be triggered.

We'll need to simulate a user moving the ScrollBar around. And maybe you know this, but Silverlight (and .NET) provide a concept that was made for what we're trying to achieve, Microsoft UI Automation. One of its features is simulating user interaction. You can read more about it here and here. The following code gets the AutomationPeer for the DataGrid and this object can provide us with the IScrollProvider.

        private static IScrollProvider GetScrollProvider(DataGrid grid)
        {
            var p = FrameworkElementAutomationPeer.FromElement(grid) 
                      ?? FrameworkElementAutomationPeer.CreatePeerForElement(grid);
            return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
        }

Finally, using this IScrollProvider you can simulate a scroll interaction:

            switch (mode)
            {
                case ScrollMode.Vertical:
                    scrollProvider.SetScrollPercent(
                      System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
                    break;
                case ScrollMode.Horizontal:
                    scrollProvider.SetScrollPercent(percent, 
                      System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
                    break;
            }

The Code 

Here is the code with all the necessary extension methods:

    public static class DataGridScrollExtensions    {
        /// <summary>        /// Scroll to the start of the ScrollBar.        /// <param name="mode"></param>        public static void ScrollToStart(this DataGrid grid, ScrollMode mode)
        {
            switch (mode)
            {
                case ScrollMode.Vertical:
                    grid.ScrollToPercent(ScrollMode.Vertical, 0);
                    break;
                case ScrollMode.Horizontal:
                    grid.ScrollToPercent(ScrollMode.Horizontal, 0);
                    break;
            }
        }
 
        /// <summary>        /// Scroll to the end of the ScrollBar.        /// </summary>        /// <param name="mode"></param>        public static void ScrollToEnd(this DataGrid grid, ScrollMode mode)
        {
            switch (mode)
            {
                case ScrollMode.Vertical:
                    grid.ScrollToPercent(ScrollMode.Vertical, 100);
                    break;
                case ScrollMode.Horizontal:
                    grid.ScrollToPercent(ScrollMode.Horizontal, 100);
                    break;
            }
        }
 
        /// <summary>        /// Scroll to a percentage of the scrollbar (50% = half).        /// </summary>        /// <param name="mode"></param>        /// <param name="percent"></param>        public static void ScrollToPercent(this DataGrid grid, ScrollMode mode, double percent)
        {
            // Fix the percentage.            if (percent < 0)
                percent = 0;
            else if (percent > 100)
                percent = 100;
 
            // Get the scroll provider.            var scrollProvider = GetScrollProvider(grid);
 
            // Scroll.            switch (mode)
            {
                case ScrollMode.Vertical:
                    scrollProvider.SetScrollPercent(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
                    break;
                case ScrollMode.Horizontal:
                    scrollProvider.SetScrollPercent(percent, System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
                    break;
            }
        }
 
        /// <summary>        /// Get the current position of the scrollbar.        /// </summary>        /// <param name="mode"></param>        /// <returns></returns>        public static double GetScrollPosition(this DataGrid grid, ScrollMode mode)
        {
            var scrollBar = grid.GetScrollbar(mode);
            return scrollBar.Value;
        }
 
        /// <summary>        /// Get the maximum position of a scrollbar.        /// </summary>        /// <param name="mode"></param>        /// <returns></returns>        public static double GetScrollMaximum(this DataGrid grid, ScrollMode mode)
        {
            var scrollBar = grid.GetScrollbar(mode);
            return scrollBar.Maximum;
        }
 
        /// <summary>        /// Scroll to a position of the scrollbar.        /// </summary>        /// <param name="mode"></param>        /// <param name="position"></param>        public static void Scroll(this DataGrid grid, ScrollMode mode, double position)
        {
            // Get the scrollbar and convert the position to percent.            var scrollBar = grid.GetScrollbar(mode);
            double positionPct = ((position / scrollBar.Maximum) * 100);
 
            // Scroll to a specfic percentage of the scrollbar.            grid.ScrollToPercent(mode, positionPct);
        }
 
        /// <summary>        /// Get a scroll provider for the grid.        /// </summary>        /// <param name="element"></param>        /// <returns></returns>        private static IScrollProvider GetScrollProvider(DataGrid grid)
        {
            var p = FrameworkElementAutomationPeer.FromElement(grid) ?? FrameworkElementAutomationPeer.CreatePeerForElement(grid);
            return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
        }
 
        /// <summary>        /// Get one of the grid's scrollbars.        /// </summary>        /// <param name="grid"></param>        /// <param name="mode"></param>        /// <returns></returns>        public static ScrollBar GetScrollbar(this DataGrid grid, ScrollMode mode)
        {
            if (mode == ScrollMode.Vertical)
                return grid.GetScrollbar("VerticalScrollbar");
            else                return grid.GetScrollbar("HorizontalScrollbar");
        }
        
        /// <summary>        /// Find the scrollbar for our datagrid.        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="dep"></param>        /// <returns></returns>        private static ScrollBar GetScrollbar(this DependencyObject dep, string name)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
            {
                var child = VisualTreeHelper.GetChild(dep, i);
                if (child != null && child is ScrollBar && ((ScrollBar)child).Name == name)
                    return child as ScrollBar;
                else                {
                    ScrollBar sub = GetScrollbar(child, name);
                    if (sub != null)
                        return sub;
                }
            }
            return null;
        } 
    }

Creating the ScrollDataGrid control

Now that we can control the scrolling we could go even further. What about creating a DataGrid where you can subscribe to both Scroll events or even store the current scroll position? This could be useful for when we're reloading the items and we want to preserve the scroll position.

The most important things to note are the SaveScrollPosition ReloadScrollPosition methods and the HorizontalScroll VerticallScroll events. Here is a small demo of the ScrollDataGrid:

 

The method SaveScrollPosition saves the current position in an internal field and the ReloadScrollPosition applies the value of that field back to the ScrollBar. To find the ScrollBars we're applying an other technique, using GetTemplateChild. The rest of the code calls our extension methods:

    public class ScrollDataGrid : DataGrid    {
        /// <summary>        /// The vertical scrollbar.        /// </summary>        private ScrollBar verticalScrollBar;
 
        /// <summary>        /// The horizontal scrollbar.        /// </summary>        private ScrollBar horizontalScrollBar;
 
        /// <summary>        /// Position of the vertical scrollbar we saved.        /// </summary>        private double savedVerticalScrollPosition;
 
        /// <summary>        /// Position of the horizontal scrollbar we saved.        /// </summary>        private double savedHorizontalScrollPosition;
        
        /// <summary>        /// Event for each vertical scroll.        /// </summary>        public event EventHandler<ScrollEventArgs> VerticalScroll;
 
        /// <summary>        /// Event for each horizontal scroll.        /// </summary>        public event EventHandler<ScrollEventArgs> HorizontalScroll;
 
        /// <summary>        /// Load the scrollbars after the template gets loaded.        /// </summary>        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.LoadScrollBars();
        }
 
        /// <summary>        /// Get both scrollbars.        /// </summary>        private void LoadScrollBars()
        {
            verticalScrollBar = this.GetTemplateChild("VerticalScrollbar"as ScrollBar;
            if (verticalScrollBar != null)
                verticalScrollBar.Scroll += new ScrollEventHandler(OnVerticalScroll);
            horizontalScrollBar = this.GetTemplateChild("HorizontalScrollbar"as ScrollBar;
            if (horizontalScrollBar != null)
                horizontalScrollBar.Scroll += new ScrollEventHandler(OnHorizontalScroll);
        }
        
        /// <summary>        /// Notify that we are scrolling vertically.        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void OnVerticalScroll(object sender, ScrollEventArgs e)
        {
            if (VerticalScroll != null)
                VerticalScroll(sender, e);
        }
 
        /// <summary>        /// Notify that we are scrolling horizontally.        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void OnHorizontalScroll(object sender, ScrollEventArgs e)
        {
            if (HorizontalScroll != null)
                HorizontalScroll(sender, e);
        }
        
        /// <summary>        /// Save the current scroll position.        /// </summary>        /// <param name="mode"></param>        public void SaveScrollPosition(ScrollMode mode)
        {
            switch (mode)
            {
                case ScrollMode.Vertical:
                    this.savedVerticalScrollPosition = verticalScrollBar.Value;
                    break;
                case ScrollMode.Horizontal:
                    this.savedHorizontalScrollPosition = horizontalScrollBar.Value;
                    break;
                default:
                    break;
            }
        }
 
        /// <summary>        /// Reload the scroll position that was saved before.        /// </summary>        /// <param name="mode"></param>        public void ReloadScrollPosition(ScrollMode mode)
        {
            switch (mode)
            {
                case ScrollMode.Vertical:
                    this.Scroll(ScrollMode.Vertical, savedVerticalScrollPosition);
                    break;
                case ScrollMode.Horizontal:
                    this.Scroll(ScrollMode.Horizontal, savedHorizontalScrollPosition);
                    break;
            }
        }
    }

Sample Project Source

Hope help this and thank for reading.

 
Sign Up to vote for this article
 
About Author
 
Dhananjay Kumar
Occupation-Software Engineer
Company-Infosys Technolgies,Pune
Member Type-Gold
Location-India
Joined date-20 Jul 2009
Home Page-http://dhananjaykumar.net/
Blog Page-http://dhananjaykumar.net/
Dhananjay Kumar is Microsoft MVP on connected system. He blogs at http://dhananjaykumar.net/ . You can follow him http://twitter.com/debugmode_/ and reach him at dhananjay.25july@gmail.com
 
 
Other popularSectionarticles
Comments
By:Viewing the ScrollBarDate Of Posted:2/2/2011 4:57:39 AM
Adding Library
How did you add a library on your source?
Leave a Reply
Title:
Display Name:
Email:
(not display in page for the security purphase)
Website:
Message:
Please refresh your screen using Ctrl+F5
If you can't read this number refresh your screen
Please input the anti-spam code that you can read in the image.
^ Scroll to Top