Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Getting Scroll Events for a Listbox

0.00/5 (No votes)
27 Jun 2004 4  
The standard framework won't tell when the scrollbar moved or what position it is at. This code gives you that control.

Sample Image - ScrollingListbox.png

Introduction

This example shows how to access scrollbar events in a custom control that is derived from the System.Windows.Forms.ListBox control, in C# .NET. Questions answered are:

  • How do I get the Scroll event?
  • How do I get the Scrollbar position?

Getting Started

Create a new project (File, New, Project...) of type 'Windows Application'. Visual Studio will show the project in your Solution Explorer and 'Form1.cs' in design mode. Right-click the project node and select 'Add, Add User Control...'. Name it 'ScrollingListBox.cs'. It is now visible in the Solution Explorer and it looks like a gray square in the designer.

Press F7 to go to the code and replace the base 'UserControl' by 'ListBox'. Save, compile, go to designer. As you can see, the icon in the Solution Explorer changes and the designer doesn't show a listbox. This is your code at this point:

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace WebsiteSamples
{
    /// <summary>

    /// Summary description for ScrollingListBox.

    /// </summary>

    public class ScrollingListBox : System.Windows.Forms.ListBox
    {
        /// <summary> 

        /// Required designer variable.

        /// </summary>

        private System.ComponentModel.Container components = null;

        public ScrollingListBox()
        {
            // This call is required by the Windows.Forms Form Designer.

            InitializeComponent();

            // TODO: Add any initialization after the InitForm call


        }

        /// <summary> 

        /// Clean up any resources being used.

        /// </summary>

        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Component Designer generated code
        /// <summary> 

        /// Required method for Designer support - do not modify 

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        #endregion
    }
}

Win32 Stuff

We are going to override the WndProc and handle the scrollbar events. Here're the constants with the same name as in the C:\Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include\WinUser.h include file.

        private const int WM_HSCROLL = 0x114;
        private const int WM_VSCROLL = 0x115;

        private const int SB_LINELEFT = 0;
        private const int SB_LINERIGHT = 1;
        private const int SB_PAGELEFT = 2;
        private const int SB_PAGERIGHT = 3;
        private const int SB_THUMBPOSITION = 4;
        private const int SB_THUMBTRACK = 5;
        private const int SB_LEFT = 6;
        private const int SB_RIGHT = 7;
        private const int SB_ENDSCROLL = 8;

        private const int SIF_TRACKPOS = 0x10;
        private const int SIF_RANGE = 0x1;
        private const int SIF_POS = 0x4;
        private const int SIF_PAGE = 0x2;
        private const int SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;

When we recognize the scroll message in the WndProc, we need to call GetScrollInfo in user32.dll to find out the position. (The Microsoft documentation says that this position is sent in the wparam of the scroll message when the type is SB_THUMBPOSITION or SB_THUMBTRACK, but that's simply not true.) So, first GetScrollInfo is imported from the DLL.

        [DllImport("user32.dll", SetLastError=true) ]
        private static extern int GetScrollInfo(
            IntPtr hWnd, int n, ref ScrollInfoStruct lpScrollInfo );

The ScrollInfoStruct is called SCROLLINFO in Win32 and it's the typical query construct you see in Microsoft APIs: an info structure is loaded with its own size and a question-code (and optionally arguments for the question). This be the case, the question is 'what's your position?', or 'SIF_ALL'.

        private struct ScrollInfoStruct
        {
            public int cbSize;
            public int fMask;
            public int nMin;
            public int nMax;
            public int nPage;
            public int nPos;
            public int nTrackPos;
        }

Finally, detect the message and fire the event

OK, now let's fire the standard ScrollEvent when we receive a scroll message. For this, we need the event, which I called it 'Scrolled', since this is the naming that Microsoft uses for events (like 'Clicked'):

        [Category("Action")]
        public event ScrollEventHandler Scrolled = null;

In the WndProc, get the scroll message and fire the Scrolled event. In this example, I only respond to the WM_HSCROLL message, since that's the one needed for an owner drawn listbox control. The code for WM_VSCROLL would be exactly the same, however. In this example, I'm only interested in the end-scroll message, which is fired after every scrolling action.

        protected override void WndProc(ref System.Windows.Forms.Message msg)
        {
            if( msg.Msg == WM_HSCROLL )
            {
                if( Scrolled != null )
                {
                    ScrollInfoStruct si = new ScrollInfoStruct();
                    si.fMask = SIF_ALL;
                    si.cbSize = Marshal.SizeOf(si);
                    GetScrollInfo(msg.HWnd, 0, ref si);

                    if( msg.WParam.ToInt32() == SB_ENDSCROLL )
                    {
                        ScrollEventArgs sargs = new ScrollEventArgs(
                            ScrollEventType.EndScroll,
                            si.nPos);
                        Scrolled(this, sargs);
                    }
                }
            }
            base.WndProc(ref msg);
        }

To be able to use the Marshal class, you have to reference the Interop namespace:

using System.Runtime.InteropServices;

That should do it. You can extend it by capturing the vertical scrollbar or by capturing more scroll events. If you want to know the scrollbar position at any other time, use the GetScrollInfo function.

Also, I haven't tried it, but it probably works for any control with a scrollbar in it.

In case you're wondering, I needed this code to have other controls 'scroll along' with a ListBox.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here