【发布时间】:2021-12-29 15:58:09
【问题描述】:
我正在尝试通过在准备/加载 Crystal Report 时添加加载屏幕来“增强”我的报告代码。在我开始尝试添加加载屏幕之前,我的所有报告都会正常显示,但光标的变化并不足以表明应用程序仍在拉动报告——其中一些可能需要一段时间 - 所以我想提供一个更“明显”的视觉提示。
为了实现这一点,我将报告创建方法调用放入加载屏幕本身中存在的BackgroundWorker 中(我还没有开始学习如何很好地使用Async/Await但使用它感觉很舒服)。加载屏幕正确出现,一切似乎都按预期工作,直到它实际尝试在屏幕上显示报告。此时,“请稍候,文档正在处理。”框出现(在用于显示报告的表单中的 CrystalReportViewer 控件中),但它只是坐在那里,甚至不旋转。最终,我的 IDE 抛出关于接收 ContextSwitchDeadlock 的错误,我几乎只需要取消执行。
这是我的 dlgReportLoading“启动画面”,带有一个包含动画 GIF 的 PictureBox 控件:
Imports System.Windows.Forms
Public Class dlgReportLoading
Private DisplayReport As Common.CRReport
Private WithEvents LoadReportWorker As System.ComponentModel.BackgroundWorker
Public Sub New(ByRef Report As Common.CRReport)
InitializeComponent()
DisplayReport = Report
End Sub
Private Sub dlgReportLoading_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Cursor = Cursors.WaitCursor
Me.TopMost = True
Me.TopMost = False
LoadReportWorker = New System.ComponentModel.BackgroundWorker
LoadReportWorker.RunWorkerAsync()
End Sub
Private Sub dlgReportLoading_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
Me.Cursor = Cursors.Default
End Sub
Private Sub LoadReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles LoadReportWorker.DoWork
If Not DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None Then
Select Case DisplayReport.ReportOption
Case Common.CRReport.GenerateReportOption.DisplayOnScreen
'-- This is the method I'm currently testing
DisplayReport.ShowReport()
Case Common.CRReport.GenerateReportOption.SendToPrinter
DisplayReport.PrintReport()
Case Common.CRReport.GenerateReportOption.ExportToFile
DisplayReport.ExportReport()
End Select
End If
DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None
'--
'-- This code was in use before trying to generate the reports in the background
'If Not DisplayReport.CrystalReport Is Nothing Then
' DisplayReport.CrystalReport.Dispose()
'End If
'--
End Sub
Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
End Class
如上面代码中所述,我目前正在测试此处定义的ShowReport() 方法:
Protected Friend Sub ShowReport()
Dim ReportViewer As frmReportPreview
Me.PrepareReport()
ReportViewer = New frmReportPreview(Me)
With ReportViewer
.WindowState = FormWindowState.Maximized
.Show()
End With
End Sub
frmReportPreview 是这样的:
Imports System.ComponentModel
Public Class frmReportPreview
Private DisplayReport As Common.CRReport
Private ReportToDisplay As CrystalDecisions.CrystalReports.Engine.ReportDocument
Public Sub New(ByRef Report As Common.CRReport)
InitializeComponent()
DisplayReport = Report
PrepareReportForDisplay()
Me.rptViewer.ReportSource = Nothing
Me.rptViewer.ReportSource = ReportToDisplay
' SET ZOOM LEVEL FOR DISPLAY:
' 1 = Page Width
' 2 = Whole Page
' 25-100 = zoom %
Me.rptViewer.Zoom(1)
Me.rptViewer.Show()
End Sub
Private Sub frmReportPreview_Shown(sender As Object, e As EventArgs) Handles Me.Shown
'-- HANGS HERE
Me.rptViewer.RefreshReport()
End Sub
Private Sub frmReportPreview_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
ReportToDisplay.Dispose()
Me.rptViewer.ReportSource = Nothing
End Sub
'...CODE FOR PREPARING THE REPORT TO BE DISPLAYED
End Class
dlgReportLoading 表单正确弹出并播放动画,直到 frmReportPreview 在其前面弹出(它不会关闭)。带有通常是动画旋转圆圈的小框显示正在加载报告数据,但几乎立即冻结在原地。
在调用ShowReport() 方法后,我的dlgReportLoading 表单的LoadReport_DoWork() 方法中有一个断点,但它永远不会到达那个点。我在该表单的 LoadReport_Complete() 方法中也有一个,它从来没有命中过,而且该对话框也从未真正关闭。
我在frmReportPreview_Shown 方法的末尾放置了另一个断点,就在Me.rptViewer.RefreshReport() 调用之后,但它也从来没有遇到过,所以很明显这是卡住的地方,但只有 当通过BackgroundWorker 生成报告时。如果我只是调用ShowReport()方法而不通过“启动画面”和BackgroundWorker发送它,一切都会正常生成并显示。
我尝试将RefreshReport() 方法放入其自己的 BackgroundWorker 中,但行为没有改变。我尝试使用ShowDialog() 而不是仅Show() 以模态方式显示frmReportPreview 对象。这些似乎都没有帮助解决这个问题。
我感觉某处有些东西被处理得太早了,但我不知道那会是什么。如果需要,我可以从frmReportPreview 提供报告准备代码的其余部分,但据我所知,所有 似乎 都在正常工作。我不反对尝试其他方法来实现我的目标,即在所有报告准备过程中向用户显示加载屏幕 - 例如,Async/Await 或其他多线程方法 - 所以 any 欢迎提出建议。如果需要任何其他说明,请告诉我。
环境
Microsoft Windows 10 Pro 21H1(操作系统内部版本 19043.1348)
Microsoft Visual Studio 社区 2017 (v15.9.38)
Crystal Reports for .NET Framework v13.0.3500.0(运行时版本 2.0.50727)
编辑:我忘了提到这整个混乱是从我的CRReport 类中的GenerateReport() 方法调用的,定义为:
Public Sub GenerateReport(ByVal ReportGeneration As GenerateReportOption)
Me.ReportOption = ReportGeneration
If Me.ReportOption = GenerateReportOption.None Then
'...CODE FOR REQUESTING A GENERATION OPTION FROM THE USER
End If
Dim ReportLoadingScreen As New dlgReportLoading(Me)
ReportLoadingScreen.ShowDialog()
End Sub
这反过来又是从我的主窗体中调用的,如下所示:
Private Sub PrintMyXMLReport(ByVal XMLFile As IO.FileInfo)
Dim MyXMLReport As New IO.FileInfo("\\SERVER\Applications\Reports\MyXMLReport.rpt")
Dim Report As New Common.CRReport(MyXMLReport, XMLFile)
Report.GenerateReport(Common.CRReport.GenerateReportOption.DisplayOnScreen)
End Sub
【问题讨论】:
-
TBH 有很多代码要看,所以乍一看我并没有真正了解你在做什么。我看到了样本,看起来它应该可以工作。但是我不喜欢在加载屏幕中定义后台工作人员的想法,这将是我对该示例的批评。为了分离 UI 和业务逻辑,调用者不应该知道 UI 存在。我不确定这种跨 UI 和非 UI 线程调用的线程顺序是否与它有关,但我个人不会根据该示例调用它。
-
好的,现在我实际上已经查看了您的代码 :) 并且您知道
BackgroundWorker是在非 UI 线程上运行的,至少 LoadReport_DoWork 是,所以您不应该做 UI操作。但是在ShowReport中,您创建了一个新表单并显示它。您应该在 UI 上调用该调用。为此,您需要对 UI 上的表单或控件进行某种引用。但是,我认为最好显示来自LoadReport_Complete方法的报告,因为它会被调用回调用者,如果我没记错的话,这是对话框并在你的情况下在 UI 上运行。 -
我觉得你应该把
Protected Friend Sub ShowReport() Me.PrepareReport() ReportViewer.Show()拆分成两个方法:PrepareReport和ShowReport,其中的内容应该是不言而喻的,然后在LoadReport_DoWork调用prepare report,在LoadReport_Complete。在重新开始之前,请先尝试一下。我很想看看这是否有效。 -
天啊!你是一个可怕的救生员!!! (而不是糖果)
PrepareReport()方法已经是独特的(但私有的),所以我将其更改为Protected Friend方法,将 that 放入 @987654364 @handler,将其从ShowReport方法中注释掉(以避免重复),然后将整个Select Case块移动到RunWorkerCompleted处理程序中。现在一切似乎都按预期工作了!太感谢了。将该建议作为答案发布,我很乐意接受! (我也可以提供最终的代码实现):D -
太棒了。我个人只很少使用 BackgroundWorker,并且对
System.Threading.Thread和Async / Await有更多经验。但是 UI 和非 UI 之间的跨线程原则仍然适用,了解每个BackgroundWorker事件的运行方式是关键。
标签: vb.net crystal-reports backgroundworker freeze