views:

3648

answers:

4

We'd like to override DataGridView's default behavior when using a mouse wheel with this control. By default, the DataGridView scrolls a number of rows equal the SystemInformation.MouseWheelScrollLines setting. What we'd like to do is scroll just one item at a time.

(We display images in the DataGridView, which are somewhat large. Because of this scroll three rows (a typical system setting) is too much, often causing the user to scroll to items they can't even see.)

I've tried a couple things already and haven't had much success so far. Here are some issues I've run into:

  1. You can subscribe to MouseWheel events but there's no way to mark the event as handled and do my own thing.

  2. You can override OnMouseWheel but this never appears to be called.

  3. You might be able to correct this in the base scrolling code but it sounds like a messy job since other types of scrolling (e.g. using the keyboard) come through the same pipeline.

Anyone have a good suggestion?

Here's the final code, using the wonderful answer given:

 /// <summary>
 /// Handle the mouse wheel manually due to the fact that we display
 /// images, which don't work well when you scroll by more than one
 /// item at a time.
 /// </summary>
 /// 
 /// <param name="sender">
 /// sender
 /// </param>
 /// <param name="e">
 /// the mouse event
 /// </param>
 private void mImageDataGrid_MouseWheel(object sender, MouseEventArgs e)
 {
  // Hack alert!  Through reflection, we know that the passed
  // in event argument is actually a handled mouse event argument,
  // allowing us to handle this event ourselves.
  // See http://tinyurl.com/54o7lc for more info.
  HandledMouseEventArgs handledE = (HandledMouseEventArgs) e;
  handledE.Handled = true;

  // Do the scrolling manually.  Move just one row at a time.
  int rowIndex = mImageDataGrid.FirstDisplayedScrollingRowIndex;
  mImageDataGrid.FirstDisplayedScrollingRowIndex =
   e.Delta < 0 ?
    Math.Min(rowIndex + 1, mImageDataGrid.RowCount - 1):
    Math.Max(rowIndex - 1, 0);
 }
+1  A: 

I would subclass the DataGridView into my own custom control (you know, add a new Windows Forms --> Custom Control file and change the base class from Control to DataGridView).

public partial class MyDataGridView : DataGridView

Then override the WndProc method and substitute something like so:

protected override void WndProc(ref Message m)
{
    if (m.Msg == 0x20a)
    {
        int wheelDelta = ((int)m.WParam) >> 16;

        // 120 = UP 1 tick
        // -120 = DOWN 1 tick

        this.FirstDisplayedScrollingRowIndex -= (wheelDelta / 120);
    }
    else
    {
        base.WndProc(ref m);
    }
}

Of course, you'll have the check that you don't set FirstDisplayedScrollingRowIndex to a number outside of the range of your grid etc. But this works quite well!

Richard

ZeroBugBounce
Thanks. I think this would work, even though the answer I choose was simpler.
Ken Wootton
+1  A: 

Overriding OnMouseWheel and not calling base.OnMouseWheel should work. Some wheel mice have special settings that you may need to set yourself for it to work properly. See this post http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=126295&amp;SiteID=1

Nikki9696
I did mention this but I was doing something wrong. I think this would work (assuming someone did it correctly!).
Ken Wootton
+2  A: 

I just did a little scrounging and testing of my own. I used Reflector to investigate and discovered a couple things. The MouseWheel event provides a MouseEventArgs parameter, but the OnMouseWheel() override in DataGridView casts it to HandledMouseEventArgs. This also works when handling the MouseWheel event. OnMouseWheel() does indeed get called, and it is in DataGridView's override that it uses SystemInformation.MouseWheelScrollLines.

So:

  1. You could indeed handle the MouseWheel event, casting MouseEventArgs to HandledMouseEventArgs and set Handled = true, then do what you want.

  2. Subclass DataGridView, override OnMouseWheel() yourself, and try to recreate all the code I read here in Reflector except for replacing SystemInformation.MouseWheelScrollLines with 1.

The latter would be a huge pain because it uses a number of private variables (including references to the ScrollBars) and you'd have replace some with your own and get/set others using Reflection.

Joel B Fant
+1  A: 

UPDATE: Since I've now learned that the DataGridView has a MouseWheel event, I've added a second, simpler override.

One way to accomplish this is to subclass the DataGridView and override the WndProc to add special handling of the WM_MOUSEWHEEL message.

This example catches the mouse wheel movement and replaces it with a call to SendKeys.Send.

(This is a little different than just scrolling, since it also selects the next/previous row of the DataGridView. But it works.)

public class MyDataGridView : DataGridView
{
    private const uint WM_MOUSEWHEEL = 0x20a;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MOUSEWHEEL)
        {
            var wheelDelta = ((int)m.WParam) >> 16;

            if (wheelDelta < 0)
            {
                SendKeys.Send("{DOWN}");
            }

            if (wheelDelta > 0)
            {
                SendKeys.Send("{UP}");
            }

            return;
        }

        base.WndProc(ref m);
    }
}

2nd take (with the same caveats as mentioned above):

public class MyDataGridView : DataGridView
{
    protected override void OnMouseWheel(MouseEventArgs e)
    {
        if (e.Delta < 0)
            SendKeys.Send("{DOWN}");
        else
            SendKeys.Send("{UP}");
    }
}
Lette