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
{
public class ScrollingListBox : System.Windows.Forms.ListBox
{
private System.ComponentModel.Container components = null;
public ScrollingListBox()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
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
.