WinForms ReportViewer:鼠标滚轮滚动两页而不是一页

WinForms ReportViewer: mouse wheel scrolls two pages instead of one

我在 WinForms 应用程序中使用 ReportViewer 控件来显示 RDLC 报告。

表单非常简单(里面只有报表查看器,没有其他任何东西),而且通常效果很好。

但有一个烦人的问题:当用户使用鼠标滚轮更改页面时,每次滚动 up/down 会翻两页 backwards/forwards,而不是一页.

当然,这让阅读报告变得非常烦人。为什么要这样做,我怎样才能让它只滚动一页?

编辑:通过进一步测试,我可以确认 ReportViewerPageNavigation 事件针对滚轮的每个 "tick" 触发两次。还是不知道为什么...

这是一个很有趣的问题。

ReportViewer 的滚动触发器可能会使用正常显示模式的分页,无论它是如何定义的。在此显示模式下,报告者将仅在明确定义时添加分页符或添加由 InteractiveSize 属性.

确定的软分页符

您可以将 InteractiveSize 属性 设置为与 PageSize 相同,这将允许您使用 DisplayMode.Normal 并仍然保持与DisplayMode.PrintLayout.

然而,这可能仍然无法解决滚动问题,因为 ReportViewer 滚动可能也会忽略软分页符。也就是说先在单页滚动,然后再应用分页,因此有可能跳页。
如果是这种情况,那么我相信您唯一的其他选择是编写自己的 ReportViewer 自定义版本并尝试修复它。


编辑: 事实证明滚动行为对我来说工作正常。这意味着它必须是特定于您的环境的问题。您使用的是什么版本和框架?

您可以通过捕获滚动事件并自己触发PageNavigation来轻松解决。

我也遇到了同样的问题。每次使用鼠标滚轮时,PageNavigation 事件都会触发两次。

我解决了自定义 PageNavigation 事件:

    bool scroll = true;

    private void ReportViewer_PageNavigation(object sender, PageNavigationEventArgs e)
    {
        if (!scroll)
            e.Cancel = true;

        scroll = !scroll;
    }

这是我的解决方案,在 VB 中。它也适用于按钮导航。

首先,我们必须向报表查看器的导航按钮添加处理程序。 我的控件报告查看器的名称是“reportViewer

Private Sub Print_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Dim ts As ToolStrip = TryCast(reportViewer.Controls.Find("toolstrip1", True).First, ToolStrip)
        If Not ts Is Nothing Then
            For Each tsb As ToolStripItem In ts.Items
                If TypeOf tsb Is ToolStripButton AndAlso
                (TryCast(tsb, ToolStripButton).Name = "firstPage" Or
                TryCast(tsb, ToolStripButton).Name = "lastPage" Or
                TryCast(tsb, ToolStripButton).Name = "previousPage" Or
                TryCast(tsb, ToolStripButton).Name = "nextPage") Then
                    AddHandler tsb.Click, AddressOf tsb_Click
                End If
            Next
        End If
End Sub

然后是核心

Dim scrolled As New List(Of Integer)

Private Sub reportViewer_PageNavigation(sender As Object, e As PageNavigationEventArgs) Handles reportViewer.PageNavigation
    scrolled.Add(e.NewPage)
End Sub

Private Sub reportViewer_MouseWheel(sender As Object, e As MouseEventArgs) Handles reportViewer.MouseWheel
    If scrolled.Count > 1 Then
        reportViewer.CurrentPage = scrolled.Item(scrolled.Count - 2)
        scrolled.Clear()
    ElseIf scrolled.Count = 1 Then
        reportViewer.CurrentPage = scrolled.Item(scrolled.Count - 1)
        scrolled.Clear()
    End If
End Sub

Public Sub tsb_Click(ByVal sender As Object, ByVal e As EventArgs)
    scrolled.Clear()
End Sub

希望对您有所帮助

编辑:只有当您的应用程序是 运行 32 位时,原始堆栈跟踪才会显示为真。在 64 位的情况下,第二次调用中只有三行也没有出现在第一次调用中,这里列出。

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)

处理这两种情况的更新代码是

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

这仍然是一个非常丑陋的 hack,但它是有效的。


要查看这里到底发生了什么,您需要禁用 "Just My Code" 并调试到框架中。通过订阅 ReportViewer.PageNavigation 事件并设置断点,您可以看到导航发生了两次。第一次由 WM_MOUSEWHEEL 消息触发,该消息由 ReportViewer 的一个名为 RenderingPanel 的组件处理。该组件的 OnMouseWheel 方法仅调用 ReportPanel.OnMouseWheel,它执行 scroll/navigation。但是,相同的 window 消息随后被传递到 ReportPanel,导致再次调用 ReportPanel.OnMouseWheel。本质上,每个低级别的轮子消息都会产生两个卷轴。您可以通过在第一次调用期间插入 ReportPanel.OnMouseWheel 并设置 e.Handled = true 来对此进行测试。在这种情况下,不会对 ReportPanel.OnMouseWheel 进行第二次调用。

请注意,我们讨论的是 ReportViewer 内部组件的鼠标滚轮事件,这与控件的滚轮事件不同。尽管有所有内部结构,ReportViewer.MouseWheel 只引发一次,并且发生在两个 PageNavigation 事件之后。

这里的底线是控件总是处理轮消息两次。通常,您不会注意到这一点。如果您向下滚动并且报告滚动了 10%,您怎么知道它实际上只应该滚动 5% 但实际上滚动了两次?但是,如果您的缩放级别足以使单个滚轮滚动等于整个页面,那么......繁荣......每个滚轮滚动两页。但是,我还没有确定为什么这只是打印预览模式下的问题。

对此没有彻底的修复。我想到的最好办法是检查 ReportViewer.PageNavigation 内部的堆栈,如果事件是由 RenderingPanel 引起的,则忽略它。这样的修复看起来像这样:

/* 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");