Search code examples
c#winformsrdlcreportviewer

WinForms ReportViewer: mouse wheel scrolls two pages instead of one


I'm using a ReportViewer control in my WinForms application to display a RDLC report.

The form is etremely simple (it only has the reportviewer in it, and nothing else) and generally it works quite well.

But there is one annoying issue: when the user uses the mouse wheel to change the page, every scroll up/down goes two pages backwards/forwards, rather than one.

This makes reading the report very annoying, of course. Why is it doing this, and how can I make it scroll just one page?

EDIT: by testing further I can confirm that the ReportViewer's PageNavigation event fires twice for each "tick" of the scroll wheel. Still don't know why...


Solution

  • EDIT: The original stack trace appears to only be true if your application is running as 32-bit. In the case of 64-bit, there are only three lines in the second call that do not also appear in the first, which are listed here.

    at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
    

    The updated code to handle both scenarios is

    private void Viewer_PageNavigation(object sender, PageNavigationEventArgs e) => e.Cancel = Environment.StackTrace.Contains("DefWndProc");
    

    This is still quite an ugly hack, but it is effective.


    To see what's really going on here, you need to disable "Just My Code" and debug into the Framework. By subscribing to the ReportViewer.PageNavigation event and setting a breakpoint, you can see navigation occurring twice. The first time is triggered by a WM_MOUSEWHEEL message that is handled by a component of the ReportViewer called RenderingPanel. That component's OnMouseWheel method just calls ReportPanel.OnMouseWheel, which performs scroll/navigation. However, the same window message is then passed on to the ReportPanel, resulting in ReportPanel.OnMouseWheel being called again. Essentially, every low-level wheel message results in two scrolls. You can test this by breaking in ReportPanel.OnMouseWheel and setting e.Handled = true during the first call. In that case, a second call to ReportPanel.OnMouseWheel is not made.

    Note that we are talking about mouse wheel events for internal components of the ReportViewer and this is not the same as the control's wheel event. Despite all of the internals, ReportViewer.MouseWheel is only raised once and that occurs after both PageNavigation events.

    Bottom line here is that the control always processes the wheel message twice. Normally, you wouldn't notice this. If you wheel down and the report scrolls by 10%, how would you know that it was really only supposed to scroll by 5% but it actually scrolled twice? However, if your zoom level is sufficient so that a single wheel scroll equals a whole page then ... boom ... two pages per wheel scroll. I have not, however, gone so far as to determine why this is only an issue in print preview mode.

    There's no clean fix for this. The best I came up with was to examine the stack inside of ReportViewer.PageNavigation and if the event is being caused by the RenderingPanel then ignore it. Such a fix looks like this:

    /* The ReportViewer control is composed of several sub-controls. One of those is a RenderingPanel, whose OnMouseWheel method gets called
     * by the Framework in response to a window message. That method simply calls ReportPanel.OnMouseWheel, which performs scroll/navigation.
     * However, ReportPanel.OnMouseWheel is subsequently called again by the Framework in response to the same window message resulting in all
     * wheel events being processed twice. While this results in each wheel scroll being twice as large as it should be, it is generally not
     * noticeable (how would you know that the scroll distance should be half of what it is?) unless the zoom level is sufficient (i.e. Whole Page)
     * that a single wheel event results in an entire page navigation. In this case, two pages are flipped instead of one. In order to avoid this,
     * we simply cancel any page navigation that was caused by the RenderingPanel's handling of the wheel event. */    
    private void Viewer_PageNavigation(object sender, PageNavigationEventArgs e) => e.Cancel = Environment.StackTrace.Contains("ReportPanel.RenderingPanel.OnMouseWheel");