WinForms ReportViewer:鼠标滚轮滚动两页而不是一页
WinForms ReportViewer: mouse wheel scrolls two pages instead of one
我在 WinForms
应用程序中使用 ReportViewer
控件来显示 RDLC
报告。
表单非常简单(里面只有报表查看器,没有其他任何东西),而且通常效果很好。
但有一个烦人的问题:当用户使用鼠标滚轮更改页面时,每次滚动 up/down 会翻两页 backwards/forwards,而不是一页.
当然,这让阅读报告变得非常烦人。为什么要这样做,我怎样才能让它只滚动一页?
编辑:通过进一步测试,我可以确认 ReportViewer
的 PageNavigation
事件针对滚轮的每个 "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");
我在 WinForms
应用程序中使用 ReportViewer
控件来显示 RDLC
报告。
表单非常简单(里面只有报表查看器,没有其他任何东西),而且通常效果很好。
但有一个烦人的问题:当用户使用鼠标滚轮更改页面时,每次滚动 up/down 会翻两页 backwards/forwards,而不是一页.
当然,这让阅读报告变得非常烦人。为什么要这样做,我怎样才能让它只滚动一页?
编辑:通过进一步测试,我可以确认 ReportViewer
的 PageNavigation
事件针对滚轮的每个 "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");