About Me

My photo
I'm a colonist who has declared war on machines and intend to conquer them some day. You'll often find me deep in the trenches fighting off bugs and ugly defects in code. When I'm not tappity-tapping at my WMD (also, known as keyboard), you'll find me chatting with friends, reading comics or playing a PC game.

Tuesday, September 22, 2009

Take Control of your Scroll

I recently ran into a specific issue when developing for a Windows Mobile environment. I had a lot of controls inside a Windows Form and by setting the Form's AutoScroll property to true, I could automatically scroll the contents of my form. This was quick and easy but then I found that the scrolling happened one pixel at a time which was just too slow. It would not really matter in most cases but in this case, I had to support the TouchFlo scrolling gesture. Thus began a 2-day hunt for an appropriate solution.

Attempt #1:
The first thing I looked for was some sort of property or method which could increase the scroll step. Nothing.

Attempt #2:
The next thing to try was take complete control of the scrolling. I put in my own scrollbar and handled the Scroll event of the scrollbar. This was a reasonable approach but then I realized that the Touch Flo scroll gesture did not work for me. I slapped my forehead and proceeded to the next attempt.

Attempt #3 (Final Solution):
The only other real solution was to take control of the Windows pumping messages. It turns out that you can override the WndProc function for Windows Forms. When it comes to the .NET Compact Framework, though, you can’t do that because its just not supported. I googled for an alternative and found this. I really must thank Mike Underhill for bailing me out here.
Third time’s a charm, they say and it seemed true in my case. This is what I ended up with.


private const int WM_VSCROLL = 0x115;
private const int GWL_WNDPROC = -4;
private const int SB_LINEUP = 0;
private const int SB_LINEDOWN = 1;

delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam);

//Win32 APIs that I need.
[DllImport("coredll.dll", EntryPoint = "GetWindowLong")]
private static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("coredll.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newWndProc);

[DllImport("coredll.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd,
uint Msg, IntPtr wParam, IntPtr lParam);

private static IntPtr oldWndProc = IntPtr.Zero;
private static WndProcDelegate newWndProc;
private const long lowOrderMask = 0xF;

public IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == WM_VSCROLL)
{
long val = ((long)wParam & lowOrderMask);

if (val == SB_LINEDOWN)
{
//Haven’t really found out why the Y position
//was going negative but this seemed an easy fix. :p
AutoScrollPosition =
new Point(0, - AutoScrollPosition.Y + step);
}
else if (val == SB_LINEUP)
{
AutoScrollPosition =
new Point(0, - AutoScrollPosition.Y - step);
}


return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
}

//Constructor
public Form1()
{
InitializeComponent();

newWndProc = new WndProcDelegate(WndProc);
oldWndProc = GetWindowLong(pnlContainer.Handle, GWL_WNDPROC);
SetWindowLong(pnlContainer.Handle, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc));
}


Hope this helps someone who ever comes across this problem.

No comments: