Crystal 报告在循环中创建时不会使用更新的参数 Value/Data 源

Crystal Report Won't Use Updated Parameter Value/Data Source When Created In Loop

我创建了一个库,试图处理在 Visual Studio (VB.NET) 项目中使用 Crystal 报告的所有 "quirks"。我收集了过去给我带来挑战的所有元素 - setting/updating 参数和公式值、打印(包括页面范围)和设置登录凭据 - 并将它们放入似乎都可以重用的方法中当我单独生成报告时效果很好。

但是,我 运行 进入了一个场景,我想在一个循环中重复使用相同的报告对象来打印具有不同数据的多个变体 sets/parameter(s) 这样我就可以 "easily" 重复使用相同的打印机设置和其他选项,而无需在每次迭代时重新提示用户。在这种情况下,我正在使用内部构建的 DataSet 对象(由我以外的其他人构建),并且我的 Crystal 报告文件的数据源指向 .xsd 文件的结构。

编辑: 忘记提及该报告是在 CR Developer v11.5.12.1838 中创建的,VB.NET 库项目针对4.7.2 .NET 框架和使用 Crystal 库的 v13.0.3500.0(运行time v2.0.50727)。

我的意图is/was 在循环外实例化一个新的报表对象,然后在循环的每次迭代中重新设置和刷新报表的数据源和参数值。不幸的是,如果我这样做,报告似乎无法正确获取参数值、更新的数据源或两者。我一直在尝试代码放置的几种变体(因为我知道完成事情的顺序对 Crystal 报告引擎来说 非常 重要),但是 none 它似乎按照我认为应该的方式工作。

如果我在循环内实例化一个新的报告对象,它将使用正确的数据源为每次迭代正确生成报告和参数值。当然,它将我的 class 的所有内部属性重置为 "default",这有点违背了目的。 ( 是的,我知道我可以将 additional/other 参数传递给构造函数来实现效果,但这似乎是一个非常 "brute-force" 的解决方案,我更愿意让它工作我的想法).


现在是一些代码

这是调用方法的最新迭代的 pared-down/obfuscated 版本(当前是按钮单击事件处理程序的一部分)。我为实例化可重用对象所做的每一次尝试似乎都会导致一些 类失败。在 this 版本中,它加载报表并正确传递参数值,但数据源完全为空,导致报表空白。在其他变体中(我现在已经丢弃了该代码),当我实际尝试 print/export/show 报告时,它失败并出现 COM 异常:Missing parameter values.

我试过分别使用 .Refresh.ReportClientDocument.VerifyDatabase 方法,但它们没有什么区别。我在运行的时候检查参数的时候,出现CRparameter/value和查询结果都已经填充了,但是任何方法在初始化之后做任何改变好像只是"break"的报道。

        Dim ReportName As String = "\SERVERNAME\Applications\Reports\ClientActiveCustomerSummary.rpt"
        Dim Report As Common.CRReport = Nothing

        Try
            ReportData = ClientDataSet.Tables("ActiveSummary").Copy
            ReportData = GetClientActiveSummaryData
        Catch ex As Exception
            MessageBox.Show(ex.Message & vbCrLf & vbCrLf &
                "Error while retrieving client customer summary report data.",
                "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

       If ReportData.Rows.Count > 0 Then
            Report = New Common.CRReport(Common.CRReport.ReportSourceType.ADODataSet, New IO.FileInfo(ReportName), ClientDataSet)

            For Each LVItem As ListViewItem In checkItemsMP
                Dim ClientQuery = From ap In ReportData.AsEnumerable
                               Where ap.Field(Of Object)("mp") = LVItem.SubItems("mp").Text
                               Order By ap.Field(Of Object)("customername")
                               Select ap

                ClientDataSet.Tables("ActiveSummary").Merge(ClientQuery.CopyToDataTable)
                Report.ReportParameters.Clear()
                Report.AddReportParameter("ClientName", LVItem.SubItems("clientname").Text)
                Report.GenerateReport()
                ClientDataSet.Tables("ActiveSummary").Clear()
                ClientQuery = Nothing
            Next LVItem

            For Each LVItem As ListViewItem In checkItemsBN
                Dim BranchName As String = LVItem.SubItems("clientname").Text & " " & LVItem.SubItems("branchname").Text
                Dim BranchQuery = From ap In ReportData.AsEnumerable
                               Where (ap.Field(Of Object)("clientname") & " " & ap.Field(Of Object)("branchname")) = BranchName
                               Order By ap.Field(Of Object)("customername")
                               Select ap

                ClientDataSet.Tables("ActiveSummary").Merge(BranchQuery.CopyToDataTable)
                Report.ReportParameters.Clear()
                Report.AddReportParameter("ClientName", BranchName)
                Report.GenerateReport()
                ClientDataSet.Tables("ActiveSummary").Clear()
                BranchQuery = Nothing
            Next LVItem
        Else
            MessageBox.Show("NO RECORDS FOUND", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
            ReportData.Dispose()
            ReportData = Nothing
        End If

显然,在这种情况下,我传递了一个 ADO.NET DataSet 对象,并使用从表单本身的 ListView 中检索的值作为报告的值单个参数。同样,如果我在循环的每次迭代中实例化一个新的 CRReport 对象,报表将使用正确的数据和参数值正常创建,但它每次都会提示用户选择报表创建选项(print/show/export,然后 - 如果选择 "print" - 再次选择要使用的打印机)。

这是报告 class。 (请理解,这是一项正在进行的工作,与 "production quality" 代码 相去甚远):

报告对象 (CRReport)

Imports System.IO
Imports CrystalDecisions.Shared
Imports CrystalDecisions.CrystalReports.Engine
Imports CrystalDecisions.ReportAppServer.DataDefModel
Imports System.Drawing
Imports System.Windows.Forms

    Public Class CRReport
        Inherits ReportDocument

        Public Enum ReportSourceType
            PostgreSQL = 1
            MySQL = 2
            ADODataSet = 3
            XML = 4
            CSV = 5
            Access = 6
        End Enum

        Public Enum GenerateReportOption
            None = 0
            DisplayOnScreen = 1
            SendToPrinter = 2
            ExportToFile = 3
            MailToRecipient = 4
        End Enum

        Public Property ReportFile As FileInfo
        Public Property ExportPath As String
        Public Property ReportParameters As List(Of CRParameter)
        Public Property ReportFormulas As List(Of CRFormula)
        Public Property SourceType As ReportSourceType

        Private Property XMLDataSource As FileInfo
        Private Property ADODataSet As System.Data.DataSet
        Private Property ReportOption As GenerateReportOption
        Private WithEvents DocumentToPrint As Printing.PrintDocument

#Region "PUBLIC METHODS"
#Region "CONSTRUCTORS"
        Public Sub New(ByVal SourceType As ReportSourceType, ByVal CurrentReportFile As FileInfo)
            Me.Initialize()
            Me.SourceType = SourceType
            Me.ReportFile = CurrentReportFile
            PrepareReport()
        End Sub

        Public Sub New(ByVal SourceType As ReportSourceType, ByVal CurrentReportFile As FileInfo, ByVal XMLFile As FileInfo)
            Me.Initialize()
            Me.SourceType = SourceType
            Me.ReportFile = CurrentReportFile
            Me.XMLDataSource = XMLFile
            PrepareReport()
        End Sub

        Public Sub New(ByVal SourceType As ReportSourceType, ByVal CurrentReportFile As FileInfo, ByVal ADODataSource As System.Data.DataSet)
            Me.Initialize()
            Me.SourceType = SourceType
            Me.ReportFile = CurrentReportFile
            Me.ADODataSet = ADODataSource
            PrepareReport()
        End Sub

        Public Sub New(ByVal SourceType As ReportSourceType, ByVal CurrentReportFile As FileInfo, ByVal CurrentExportPath As String)
            Me.Initialize()
            Me.SourceType = SourceType
            Me.ReportFile = CurrentReportFile
            Me.ExportPath = CurrentExportPath

            If Not Me.ExportPath Is Nothing AndAlso Not String.IsNullOrEmpty(Me.ExportPath) Then
                Dim ExportFile As New IO.FileInfo(Me.ExportPath)

                If Not IO.Directory.Exists(ExportFile.DirectoryName) Then
                    IO.Directory.CreateDirectory(ExportFile.DirectoryName)
                End If
            End If

            PrepareReport()
        End Sub
#End Region

        Public Sub AddReportParameter(ByVal CurrentParameterName As String, ByVal CurrentParameterValue As Object)
            If Not String.IsNullOrEmpty(CurrentParameterName) Then
                Dim NewParameter As New CRParameter(Me, CurrentParameterName, CurrentParameterValue)

                Me.ReportParameters.Add(NewParameter)
            End If
        End Sub

        Public Sub AddReportFormula(ByVal CurrentFormulaName As String, ByVal CurrentFormulaValue As Object)
            If Not String.IsNullOrEmpty(CurrentFormulaName) Then
                Dim NewFormula As New CRFormula(Me, CurrentFormulaName, CurrentFormulaValue)

                Me.ReportFormulas.Add(NewFormula)
            End If
        End Sub

        Public Sub GenerateReport(ByVal ReportOption As GenerateReportOption)
            If Me.ReportOption = GenerateReportOption.None Then
                ' THIS DIALOG IS SOLELY FOR PROMPTING THE USER FOR HOW TO GENERATE THE REPORT
                Dim ReportDialog As New dlgGenerateReport

                Me.ReportOption = ReportDialog.GetReportGenerationOption()
            End If

            If Not Me.ReportOption = GenerateReportOption.None Then
                Select Case ReportOption
                    Case GenerateReportOption.DisplayOnScreen
                        Me.ShowReport()
                    Case GenerateReportOption.SendToPrinter
                        Me.PrintReport()
                    Case GenerateReportOption.ExportToFile
                        Me.ExportReport()
                End Select
            End If
        End Sub
#End Region

#Region "PRIVATE METHODS"
        Private Sub Initialize()
            Me.ReportFile = Nothing
            Me.ExportPath = String.Empty
            Me.ADODataSet = Nothing
            Me.XMLDataSource = Nothing
            Me.ReportParameters = New List(Of CRParameter)
            Me.ReportFormulas = New List(Of CRFormula)
            Me.SourceType = ReportSourceType.XML
            Me.ReportOption = GenerateReportOption.None
        End Sub

        Private Sub PrepareReport()
            If Not Me.ReportFile Is Nothing Then
                Me.Load(Me.ReportFile.FullName)
                Me.DataSourceConnections.Clear()
                SetReportConnectionInfo()

                If Me.ReportFormulas.Count > 0 Then
                    For Each Formula As CRFormula In Me.ReportFormulas
                        Formula.UpdateFormulaField()
                    Next Formula
                End If

                If Me.ReportParameters.Count > 0 Then
                    For Each Parameter As CRParameter In Me.ReportParameters
                        Parameter.UpdateReportParameter()
                    Next Parameter
                End If

                Me.Refresh()
                Me.ReportClientDocument.VerifyDatabase()
            End If
        End Sub

        Private Sub SetReportConnectionInfo()
            If Me.SourceType = ReportSourceType.PostgreSQL Then
                Dim CRDatabase As CrystalDecisions.CrystalReports.Engine.Database = Me.Database
                Dim CRTables As CrystalDecisions.CrystalReports.Engine.Tables = CRDatabase.Tables
                Dim CRConnectionInfo As New CrystalDecisions.Shared.ConnectionInfo
                Dim DBUsername As String = Utility.GetUsername
                Dim DBPassword As String = Utility.GetPassword

                With CRConnectionInfo
                    .DatabaseName = <DATABASENAME>
                    .ServerName = <HOSTNAME>
                    .UserID = DBUsername
                    .Password = DBPassword
                End With

                For Each CRTable As CrystalDecisions.CrystalReports.Engine.Table In CRTables
                    Dim CRTableLogonInfo As CrystalDecisions.Shared.TableLogOnInfo = CRTable.LogOnInfo

                    CRTableLogonInfo.ConnectionInfo = CRConnectionInfo
                    CRTable.ApplyLogOnInfo(CRTableLogonInfo)
                Next CRTable
            ElseIf Me.SourceType = ReportSourceType.ADODataSet Then
                Dim CRDatabase As CrystalDecisions.CrystalReports.Engine.Database = Me.Database
                Dim CRTables As CrystalDecisions.CrystalReports.Engine.Tables = CRDatabase.Tables

                For Each CRTable As CrystalDecisions.CrystalReports.Engine.Table In CRTables
                    For Each ADOTable As DataTable In ADODataSet.Tables
                        If CRTable.Name.ToUpper.Trim = ADOTable.TableName.ToUpper.Trim Then
                            CRTable.SetDataSource(ADOTable)
                            Exit For
                        End If
                    Next ADOTable
                Next CRTable

                Me.ReportClientDocument.VerifyDatabase()
            ElseIf Me.SourceType = ReportSourceType.XML Then
                If Not Me.XMLDataSource Is Nothing AndAlso Me.XMLDataSource.Exists Then
                    Dim CRDatabaseAttributes As New CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag
                    Dim CRLogonProperties As New CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag
                    Dim CRConnectionDetails As New CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo
                    Dim CRTable As CrystalDecisions.ReportAppServer.DataDefModel.Table
                    Dim CRTables As CrystalDecisions.ReportAppServer.DataDefModel.Tables = Me.ReportClientDocument.DatabaseController.Database.Tables
                    Dim XMLData As New System.Data.DataSet

                    XMLData.ReadXml(Me.XMLDataSource.FullName)

                    With CRLogonProperties
                        .Add("File Path ", Me.XMLDataSource.FullName)
                        .Add("Internal Connection ID", "{be7cdac3-6a64-4923-8177-898ab55d0fa0}")
                    End With

                    With CRDatabaseAttributes
                        .Add("Database DLL", "crdb_adoplus.dll")
                        .Add("QE_DatabaseName", "")
                        .Add("QE_DatabaseType", "")
                        .Add("QE_LogonProperties", CRLogonProperties)
                        .Add("QE_ServerDescription", Me.XMLDataSource.Name.Substring(0, Me.XMLDataSource.Name.Length - Me.XMLDataSource.Extension.Length))
                        .Add("QE_SQLDB", "False")
                        .Add("SSO Enabled", "False")
                    End With

                    With CRConnectionDetails
                        .Attributes = CRDatabaseAttributes
                        .Kind = CrystalDecisions.ReportAppServer.DataDefModel.CrConnectionInfoKindEnum.crConnectionInfoKindCRQE
                        .UserName = ""
                        .Password = ""
                    End With

                    For I As Integer = 0 To XMLData.Tables.Count - 1
                        CRTable = New CrystalDecisions.ReportAppServer.DataDefModel.Table

                        With CRTable
                            .ConnectionInfo = CRConnectionDetails
                            .Name = XMLData.Tables(I).TableName
                            .QualifiedName = XMLData.Tables(I).TableName
                            .[Alias] = XMLData.Tables(I).TableName
                        End With

                        Me.ReportClientDocument.DatabaseController.SetTableLocation(CRTables(I), CRTable)
                    Next I

                    Me.ReportClientDocument.VerifyDatabase()
                End If
            End If
        End Sub

        Private Sub PrintReport()
            If Me.DocumentToPrint Is Nothing Then
                ' THIS IS WHY I WANT TO REUSE THE REPORTING OBJECT
                ' IF I CAN SET/SAVE THE PRINT DOCUMENT/SETTINGS WITHIN THE OBJECT,
                ' THE USER SHOULD ONLY HAVE TO RESPOND ONCE FOR ANY ITERATIONS
                ' USING THE SAME REPORT OBJECT
                Dim SelectPrinter As New PrintDialog
                Dim PrinterSelected As DialogResult = DialogResult.Cancel

                Me.DocumentToPrint = New Printing.PrintDocument

                With SelectPrinter
                    .Document = DocumentToPrint
                    .AllowPrintToFile = False
                    .AllowSelection = False
                    .AllowCurrentPage = False
                    .AllowSomePages = False
                    .PrintToFile = False
                    .UseEXDialog = True
                End With

                PrinterSelected = SelectPrinter.ShowDialog()

                If PrinterSelected = DialogResult.OK Then
                    SendToPrinter()
                End If
            Else
                SendToPrinter()
            End If
        End Sub

        Private Sub SendToPrinter()
            Dim Copies As Integer = DocumentToPrint.PrinterSettings.Copies
            Dim PrinterName As String = DocumentToPrint.PrinterSettings.PrinterName
            Dim LastPageNumber As Integer = 1

            ' IF THE PARAMETER VALUE DOESN'T GET PASSED/UPDATED PROPERLY
            ' THIS LINE WILL THROW A COM EXCEPTION 'MISSING PARAMETER VALUE'
            LastPageNumber = Me.FormatEngine.GetLastPageNumber(New CrystalDecisions.Shared.ReportPageRequestContext())
            Me.PrintOptions.CopyTo(DocumentToPrint.PrinterSettings, DocumentToPrint.DefaultPageSettings)

            If DocumentToPrint.PrinterSettings.SupportsColor Then
                DocumentToPrint.DefaultPageSettings.Color = True
            End If

            Me.PrintOptions.CopyFrom(DocumentToPrint.PrinterSettings, DocumentToPrint.DefaultPageSettings)
            Me.PrintOptions.PrinterName = PrinterName
            Me.PrintOptions.PrinterDuplex = CType(DocumentToPrint.PrinterSettings.Duplex, PrinterDuplex)
            Me.PrintToPrinter(Copies, True, 1, LastPageNumber)
        End Sub

        Private Function ExportReport() As IO.FileInfo
            Dim ExportFile As IO.FileInfo = Nothing

            If Not Me.ExportPath Is Nothing AndAlso Not String.IsNullOrEmpty(Me.ExportPath) Then
                ExportFile = New IO.FileInfo(Me.ExportPath)

                If Not ExportFile.Exists Then
                    Me.ExportToDisk(ExportFormatType.PortableDocFormat, ExportFile.FullName)
                Else
                    Dim Response As DialogResult = DialogResult.Cancel

                    Response = MessageBox.Show(ExportFile.Name & " already exists in this location." & vbCrLf & vbCrLf &
                                               "Do you want to overwrite the existing file?" & vbCrLf & vbCrLf &
                                               "Click [Y]ES to overwrite the existing file" & vbCrLf &
                                               "Click [N]O to create a new file" & vbCrLf &
                                               "Click [C]ANCEL to cancel the export process",
                                               "PDF ALREADY EXISTS",
                                               MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2)

                    If Response = DialogResult.Yes Then
                        ExportFile.Delete()
                    ElseIf Response = DialogResult.No Then
                        ExportFile = New IO.FileInfo(Common.Utility.IncrementExistingFileName(Me.ExportPath))
                    Else
                        ExportFile = Nothing
                    End If

                    If Not ExportFile Is Nothing Then
                        Me.ExportToDisk(ExportFormatType.PortableDocFormat, ExportFile.FullName)
                    End If
                End If
            End If

            Return ExportFile
        End Function

        Private Sub ShowReport()
            Dim ReportViewer As New frmReportPreview

            With ReportViewer
                .rptViewer.ReportSource = Nothing
                .rptViewer.ReportSource = Me
                .WindowState = FormWindowState.Maximized
                .rptViewer.RefreshReport()

                ' Set zoom level: 1 = Page Width, 2 = Whole Page, 25-100 = zoom %
                .rptViewer.Zoom(1)
                .rptViewer.Show()
                .Show()
            End With
        End Sub

        Private Sub EmailReport(ByRef ReportMail As System.Net.Mail.MailMessage)
            Dim ReportAttachment As IO.FileInfo = ExportReport()

            If Not ReportAttachment Is Nothing AndAlso ReportAttachment.Exists Then
                ReportMail.Attachments.Add(New System.Net.Mail.Attachment(ReportAttachment.FullName))

                If Utility.SendEmailMessage(ReportMail) Then

                End If
            End If
        End Sub
#End Region

我尝试在 CRReport class 的 GenerateReport 方法中(再次)添加对 PrepareReport 方法的调用,以便它会重置数据源和参数值,但它似乎仍然没有在实际的 Crystal 报告对象中正确设置所有内容以生成报告。到目前为止,我的经验是,出于某种原因,我必须在实例化时设置所有这些,否则它就会完全失败。


为了参考,Crystal的参数和公式封装在自己的class中:

参数对象 (CRParameter)

#Region "CRYSTAL REPORTS PARAMETER CLASS"
    Public Class CRParameter
        Public Property CurrentReport As CRReport
        Public Property ParameterName As String
        Public Property ParameterValue As Object

        Public Sub New(ByVal Report As CRReport)
            Me.CurrentReport = Report
            Me.ParameterName = String.Empty
            Me.ParameterValue = Nothing
        End Sub

        Public Sub New(ByVal Report As CRReport, ByVal CurrentParameterName As String, ByVal CurrentParameterValue As Object)
            Me.CurrentReport = Report
            Me.ParameterName = CurrentParameterName
            Me.ParameterValue = CurrentParameterValue
            UpdateReportParameter()
        End Sub

        Friend Sub UpdateReportParameter()
            If Not Me.CurrentReport Is Nothing Then
                If Not String.IsNullOrEmpty(Me.ParameterName) Then
                    Dim CRFieldDefinitions As ParameterFieldDefinitions = Nothing
                    Dim CRFieldDefinition As ParameterFieldDefinition = Nothing
                    Dim CRValues As ParameterValues = Nothing
                    Dim CRDiscreteValue As ParameterDiscreteValue = Nothing

                    Try
                        CRFieldDefinitions = Me.CurrentReport.DataDefinition.ParameterFields
                        CRFieldDefinition = CRFieldDefinitions.Item(Me.ParameterName)
                        CRValues = CRFieldDefinition.CurrentValues

                        CRValues.Clear()
                        CRDiscreteValue = New ParameterDiscreteValue
                        CRDiscreteValue.Description = Me.ParameterName
                        CRDiscreteValue.Value = Me.ParameterValue
                        CRValues.Add(CRDiscreteValue)
                        CRFieldDefinition.ApplyCurrentValues(CRValues)
                        CRFieldDefinition.ApplyDefaultValues(CRValues)
                    Catch ex As Exception
                        Throw
                    Finally
                        If Not CRFieldDefinitions Is Nothing Then
                            CRFieldDefinitions.Dispose()
                        End If

                        If Not CRFieldDefinition Is Nothing Then
                            CRFieldDefinition.Dispose()
                        End If

                        If Not CRValues Is Nothing Then
                            CRValues = Nothing
                        End If

                        If Not CRDiscreteValue Is Nothing Then
                            CRDiscreteValue = Nothing
                        End If
                    End Try
                End If
            End If
        End Sub
    End Class
#End Region

公式对象 (CRFormula)

我意识到这超出了原始问题的范围,但为了完整起见,我想把它包括在内,以防其他人可能正在寻找要使用的代码。

#Region "CRYSTAL REPORTS FORMULA VALUE CLASS"
    Public Class CRFormula
        Public Property CurrentReport As CRReport
        Public Property FormulaName As String
        Public Property FormulaValue As Object

        Public Sub New(ByVal Report As CRReport)
            Me.CurrentReport = Report
            Me.FormulaName = String.Empty
            Me.FormulaValue = Nothing
        End Sub

        Public Sub New(ByVal Report As CRReport, ByVal NewFormulaName As String, ByVal NewFormulaValue As Object)
            Me.CurrentReport = Report
            Me.FormulaName = NewFormulaName
            Me.FormulaValue = NewFormulaValue
            'UpdateFormulaField()
        End Sub

        Friend Sub UpdateFormulaField()
            If Not Me.CurrentReport Is Nothing Then
                If Not String.IsNullOrEmpty(Me.FormulaName) Then
                    Try
                        If Me.FormulaValue Is Nothing Then
                            Me.FormulaValue = ""
                            Me.CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                        ElseIf TypeOf Me.FormulaValue Is String AndAlso String.IsNullOrEmpty(Convert.ToString(Me.FormulaValue)) Then
                            Me.FormulaValue = ""
                            Me.CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                        ElseIf TypeOf Me.FormulaValue Is String AndAlso Not String.IsNullOrEmpty(Convert.ToString(Me.FormulaValue)) Then
                            Me.FormulaValue = "'" & Me.FormulaValue.ToString & "'"
                            Me.CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                        ElseIf TypeOf Me.FormulaValue Is Date Then
                            Me.FormulaValue = "'" & Convert.ToDateTime(Me.FormulaValue).ToString("yyyy-MM-dd") & "'"
                            Me.CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                        Else
                            Me.CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                        End If
                    Catch ex As Exception

                    End Try
                End If
            End If
        End Sub
    End Class
#End Region

我已尝试包含尽可能多的关于我面临的挑战的细节和信息,但如果您需要澄清,请随时提出任何问题。

好的,我相信我已经找到了问题的原因,这是我在问题顶部提到的 "quirks" 之一。 Crystal 报表引擎对某些事件的顺序非常讲究,这就是其中一种情况。在我原来的 PrepareReport() 方法中,我最后执行了对 .Refresh().VerifyDatabase() 方法的调用。这(显然)有效地 "resets" parameters/data 源,所以我在它上面的所有内容基本上都无效了。

所以,我回顾了一些较旧的代码,看看我过去是如何处理个人 Crystal 报告的,发现调用 .Refresh().VerifyDatabase() 方法在尝试设置参数 and/or 之前,公式值似乎按预期工作,所以我将这两行移到 PrepareReport() 代码中并再次尝试。这一切似乎都正常工作。后来进行了几次测试,执行顺序似乎是这里的罪魁祸首。现在我的 PrepareReport() 方法如下所示:

Private Sub PrepareReport()
    If Not Me.CRReportFile Is Nothing Then
        Me.CrystalReport = New CrystalDecisions.CrystalReports.Engine.ReportDocument
        Me.CrystalReport.Load(Me.CRReportFile.FullName)
        Me.CrystalReport.DataSourceConnections.Clear()
        SetReportConnectionInfo()

        'MOVED THIS UP IN THE EXECUTION ORDER
        Me.CrystalReport.Refresh()
        Me.CrystalReport.ReportClientDocument.VerifyDatabase()

        If Me.ReportFormulas.Count > 0 Then
            For Each Formula As CRFormula In Me.ReportFormulas
                Formula.UpdateFormulaField(Me.CrystalReport)
            Next Formula
        End If

        If Me.ReportParameters.Count > 0 Then
            For Each Parameter As CRParameter In Me.ReportParameters
                Parameter.UpdateReportParameter(Me.CrystalReport)
            Next Parameter
        End If

        ' THE REFRESH() & VERIFYDATABASE() METHOD CALLS USED TO BE DOWN HERE
    End If
End Sub

TL;对其他信息感兴趣的人的 DR 版本

我在排除故障时尝试了一些其他的事情,none 其中一些取得了完全成功,尽管有些产生了不同程度的结果:

  • 我尝试用 MyBase.Dispose() 处理基础对象(显然这是个坏主意)。当然,如果不完全重新创建 CRReport 对象,我就无法实例化新的基础对象,这是我一开始就试图避免的。
  • 我删除了 class 的继承并为 CrystalDecisions.CrystalReports.Engine.ReportDocument 对象创建了一个私有变量,该对象可以独立于我的 class 进行实例化。尽管看起来没有必要,但这实际上并不是一个可怕的想法,我稍后会解释。
  • 我尝试了其他几种代码放置变体,但都以某种方式失败了。

由于修改后的 PrepareReport() 代码现在似乎一切正常,因此我进行了一些重构。我没有多次调用此方法(一次在实例化时,一次在报告生成时),而是从构造函数中删除了调用,并在 GenerateReport() 方法中对其进行了一次调用。

轻微"HICCUP"

我用ShowReport()的方法做了一些额外的测试(显示在屏幕上而不是打印在纸上),还有一些"weirdness",所以我不得不做出调整。在我的调用方法(按钮单击事件)中,我尝试在生成所有报告后处理 CRReport 对象,但这导致我无法在报告 generated/displayed 后切换页面(我得到了 NullReferenceException - Object reference not set to an instance of an object)。后来做了一个小调整,我可以让报告保持实例化,但是由于数据集被以后的迭代覆盖,它并不总是在每个 window.

中显示正确的数据

这就是我取消继承的作用所在。我为 class 创建了一个私有 CrystalDecisions.CrystalReports.Engine.ReportDocument 对象,它可以重新实例化并传递一些只保留与报告的特定实例相关联的数据。我重构了 CRReportCRParameterCRFormula 对象的代码,改为使用新的私有变量,一切看起来都完全符合预期。


这是修改后的完整代码

请记住,并非所有这些都经过全面测试。我还没有测试 ExportReport() 方法 b/c 我需要在那里清理一些东西,而 EmailReport() 方法还有很长的路要走。在 运行 期间,我只使用 ADO.NET DataSet 对其进行了测试,尽管用于 XML 和 PostgreSQL 的代码在过去一直有效。

报告对象 (CRReport)

Public Class CRReport
    Public Property CRReportFile As FileInfo
    Public Property ReportParameters As List(Of CRParameter)
    Public Property ReportFormulas As List(Of CRFormula)
    Public Property SourceType As ReportSourceType

    Public Property ExportPath As String
        Get
            Return ExportReportToPath
        End Get

        Set(value As String)
            If Not value Is Nothing AndAlso Not String.IsNullOrEmpty(value) Then
                Dim ExportFile As New IO.FileInfo(value)

                If Not IO.Directory.Exists(ExportFile.DirectoryName) Then
                    IO.Directory.CreateDirectory(ExportFile.DirectoryName)
                End If

                ExportReportToPath = ExportFile.FullName
            End If
        End Set
    End Property

    Private Property XMLDataSource As FileInfo
    Private Property ADODataSet As System.Data.DataSet
    Private Property ReportOption As GenerateReportOption

    Private CrystalReport As CrystalDecisions.CrystalReports.Engine.ReportDocument
    Private ExportReportToPath As String

    Public Enum ReportSourceType
        PostgreSQL = 1
        MySQL = 2
        ADODataSet = 3
        XML = 4
        CSV = 5
        Access = 6
    End Enum

    Public Enum GenerateReportOption
        None = 0
        DisplayOnScreen = 1
        SendToPrinter = 2
        ExportToFile = 3
        MailToRecipient = 4
    End Enum

    Private WithEvents DocumentToPrint As Printing.PrintDocument

#Region "PUBLIC METHODS"
    Public Sub New(ByVal CurrentReportFile As FileInfo, ByVal XMLFile As FileInfo)
        Me.Initialize()
        Me.SourceType = ReportSourceType.XML
        Me.CRReportFile = CurrentReportFile
        Me.XMLDataSource = XMLFile
    End Sub

    Public Sub New(ByVal CurrentReportFile As FileInfo, ByVal ADODataSource As System.Data.DataSet)
        Me.Initialize()
        Me.SourceType = ReportSourceType.ADODataSet
        Me.CRReportFile = CurrentReportFile
        Me.ADODataSet = ADODataSource
    End Sub

    Public Sub AddReportParameter(ByVal CurrentParameterName As String, ByVal CurrentParameterValue As Object)
        If Not String.IsNullOrEmpty(CurrentParameterName) Then
            Dim NewParameter As New CRParameter(CurrentParameterName, CurrentParameterValue)

            Me.ReportParameters.Add(NewParameter)
        End If
    End Sub

    Public Sub AddReportFormula(ByVal CurrentFormulaName As String, ByVal CurrentFormulaValue As Object)
        If Not String.IsNullOrEmpty(CurrentFormulaName) Then
            Dim NewFormula As New CRFormula(CurrentFormulaName, CurrentFormulaValue)

            Me.ReportFormulas.Add(NewFormula)
        End If
    End Sub

    Public Sub GenerateReport()
        If Me.ReportOption = GenerateReportOption.None Then
            Dim ReportDialog As New dlgGenerateReport

            Me.ReportOption = ReportDialog.GetReportGenerationOption()
        End If

        If Not Me.ReportOption = GenerateReportOption.None Then
            GenerateReport(Me.ReportOption)
        End If
    End Sub

    Public Sub GenerateReport(ByVal ReportOption As GenerateReportOption)
        If Me.ReportOption = GenerateReportOption.None Then
            Dim ReportDialog As New dlgGenerateReport

            Me.ReportOption = ReportDialog.GetReportGenerationOption()
        End If

        If Not Me.ReportOption = GenerateReportOption.None Then
            PrepareReport()

            Select Case ReportOption
                Case GenerateReportOption.DisplayOnScreen
                    Me.ShowReport()
                Case GenerateReportOption.SendToPrinter
                    Me.PrintReport()
                Case GenerateReportOption.ExportToFile
                    Me.ExportReport()
            End Select
        End If
    End Sub

    Private Sub PrintReport()
        If Me.DocumentToPrint Is Nothing Then
            Dim SelectPrinter As New PrintDialog
            Dim PrinterSelected As DialogResult = DialogResult.Cancel

            Me.DocumentToPrint = New Printing.PrintDocument
            Me.CrystalReport.PrintOptions.CopyTo(Me.DocumentToPrint.PrinterSettings, Me.DocumentToPrint.DefaultPageSettings)

            With SelectPrinter
                .Document = DocumentToPrint
                .AllowPrintToFile = False
                .AllowSelection = False
                .AllowCurrentPage = False
                .AllowSomePages = False
                .PrintToFile = False
                .UseEXDialog = True
            End With

            PrinterSelected = SelectPrinter.ShowDialog()

            If PrinterSelected = DialogResult.OK Then
                SendToPrinter()
            End If
        Else
            SendToPrinter()
        End If
    End Sub

    Private Sub SendToPrinter()
        If Not Me.DocumentToPrint Is Nothing Then
            Dim Copies As Integer = Me.DocumentToPrint.PrinterSettings.Copies
            Dim PrinterName As String = Me.DocumentToPrint.PrinterSettings.PrinterName
            Dim LastPageNumber As Integer = 1

            LastPageNumber = Me.CrystalReport.FormatEngine.GetLastPageNumber(New CrystalDecisions.Shared.ReportPageRequestContext())
            Me.CrystalReport.PrintOptions.CopyFrom(Me.DocumentToPrint.PrinterSettings, Me.DocumentToPrint.DefaultPageSettings)
            Me.CrystalReport.PrintOptions.PrinterName = PrinterName
            Me.CrystalReport.PrintOptions.PrinterDuplex = CType(Me.DocumentToPrint.PrinterSettings.Duplex, PrinterDuplex)
            Me.CrystalReport.PrintToPrinter(Copies, True, 1, LastPageNumber)
        End If
    End Sub

    Private Function ExportReport() As IO.FileInfo
        Dim ExportFile As IO.FileInfo = Nothing

        If Not Me.ExportPath Is Nothing AndAlso Not String.IsNullOrEmpty(Me.ExportPath) Then
            ExportFile = New IO.FileInfo(Me.ExportPath)

            If Not ExportFile.Exists Then
                Me.CrystalReport.ExportToDisk(ExportFormatType.PortableDocFormat, ExportFile.FullName)
            Else
                Dim Response As DialogResult = DialogResult.Cancel

                Response = MessageBox.Show(ExportFile.Name & " already exists in this location." & vbCrLf & vbCrLf &
                                           "Do you want to overwrite the existing file?" & vbCrLf & vbCrLf &
                                           "Click [Y]ES to overwrite the existing file" & vbCrLf &
                                           "Click [N]O to create a new file" & vbCrLf &
                                           "Click [C]ANCEL to cancel the export process",
                                           "PDF ALREADY EXISTS",
                                           MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2)

                If Response = DialogResult.Yes Then
                    ExportFile.Delete()
                ElseIf Response = DialogResult.No Then
                    ExportFile = New IO.FileInfo(Common.Utility.IncrementExistingFileName(Me.ExportPath))
                Else
                    ExportFile = Nothing
                End If

                If Not ExportFile Is Nothing Then
                    Me.CrystalReport.ExportToDisk(ExportFormatType.PortableDocFormat, ExportFile.FullName)
                End If
            End If
        End If

        Return ExportFile
    End Function

    Private Sub ShowReport()
        Dim ReportViewer As New frmReportPreview

        With ReportViewer
            .rptViewer.ReportSource = Nothing
            .rptViewer.ReportSource = Me.CrystalReport
            .WindowState = FormWindowState.Maximized
            .rptViewer.RefreshReport()

            ' Set zoom level: 1 = Page Width, 2 = Whole Page, 25-100 = zoom %
            .rptViewer.Zoom(1)
            .rptViewer.Show()
            .Show()
        End With
    End Sub

    Private Sub EmailReport(ByRef ReportMail As System.Net.Mail.MailMessage)
        Dim ReportAttachment As IO.FileInfo = ExportReport()

        If Not ReportAttachment Is Nothing AndAlso ReportAttachment.Exists Then
            ReportMail.Attachments.Add(New System.Net.Mail.Attachment(ReportAttachment.FullName))

            If Utility.SendEmailMessage(ReportMail) Then

            End If
        End If
    End Sub

    Public Overloads Sub Dispose()
        Me.CrystalReport.Dispose()

        If Not Me.DocumentToPrint Is Nothing Then
            Me.DocumentToPrint.Dispose()
        End If
    End Sub
#End Region

#Region "PRIVATE METHODS"
    Private Sub Initialize()
        Me.CrystalReport = Nothing
        Me.CRReportFile = Nothing
        Me.ExportPath = String.Empty
        Me.ADODataSet = Nothing
        Me.XMLDataSource = Nothing
        Me.ReportParameters = New List(Of CRParameter)
        Me.ReportFormulas = New List(Of CRFormula)
        Me.SourceType = ReportSourceType.XML
        Me.ReportOption = GenerateReportOption.None
    End Sub

    Private Sub PrepareReport()
        If Not Me.CRReportFile Is Nothing Then
            Me.CrystalReport = New CrystalDecisions.CrystalReports.Engine.ReportDocument
            Me.CrystalReport.Load(Me.CRReportFile.FullName)
            Me.CrystalReport.DataSourceConnections.Clear()
            SetReportConnectionInfo()

            Me.CrystalReport.Refresh()
            Me.CrystalReport.ReportClientDocument.VerifyDatabase()

            If Me.ReportFormulas.Count > 0 Then
                For Each Formula As CRFormula In Me.ReportFormulas
                    Formula.UpdateFormulaField(Me.CrystalReport)
                Next Formula
            End If

            If Me.ReportParameters.Count > 0 Then
                For Each Parameter As CRParameter In Me.ReportParameters
                    Parameter.UpdateReportParameter(Me.CrystalReport)
                Next Parameter
            End If
        End If
    End Sub

    Private Sub SetReportConnectionInfo()
        If Me.SourceType = ReportSourceType.PostgreSQL Then
            Dim CRDatabase As CrystalDecisions.CrystalReports.Engine.Database = Me.CrystalReport.Database
            Dim CRTables As CrystalDecisions.CrystalReports.Engine.Tables = CRDatabase.Tables
            Dim CRConnectionInfo As New CrystalDecisions.Shared.ConnectionInfo

            With CRConnectionInfo
                .DatabaseName = <DBNAME>
                .ServerName = <HOSTNAME>
                .UserID = Utility.GetDBUsername
                .Password = Utility.GetDBPassword
            End With

            For Each CRTable As CrystalDecisions.CrystalReports.Engine.Table In CRTables
                Dim CRTableLogonInfo As CrystalDecisions.Shared.TableLogOnInfo = CRTable.LogOnInfo

                CRTableLogonInfo.ConnectionInfo = CRConnectionInfo
                CRTable.ApplyLogOnInfo(CRTableLogonInfo)
            Next CRTable
        ElseIf Me.SourceType = ReportSourceType.ADODataSet Then
            Dim CRDatabase As CrystalDecisions.CrystalReports.Engine.Database = Me.CrystalReport.Database
            Dim CRTables As CrystalDecisions.CrystalReports.Engine.Tables = CRDatabase.Tables

            For Each CRTable As CrystalDecisions.CrystalReports.Engine.Table In CRTables
                For Each ADOTable As DataTable In ADODataSet.Tables
                    If CRTable.Name.ToUpper.Trim = ADOTable.TableName.ToUpper.Trim Then
                        CRTable.SetDataSource(ADOTable)
                        Exit For
                    End If
                Next ADOTable
            Next CRTable
        ElseIf Me.SourceType = ReportSourceType.XML Then
            If Not Me.XMLDataSource Is Nothing AndAlso Me.XMLDataSource.Exists Then
                Dim CRDatabaseAttributes As New CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag
                Dim CRLogonProperties As New CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag
                Dim CRConnectionDetails As New CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo
                Dim CRTable As CrystalDecisions.ReportAppServer.DataDefModel.Table
                Dim CRTables As CrystalDecisions.ReportAppServer.DataDefModel.Tables = Me.CrystalReport.ReportClientDocument.DatabaseController.Database.Tables
                Dim XMLData As New System.Data.DataSet

                XMLData.ReadXml(Me.XMLDataSource.FullName)

                With CRLogonProperties
                    .Add("File Path ", Me.XMLDataSource.FullName)
                    .Add("Internal Connection ID", "{be7cdac3-6a64-4923-8177-898ab55d0fa0}")
                End With

                With CRDatabaseAttributes
                    .Add("Database DLL", "crdb_adoplus.dll")
                    .Add("QE_DatabaseName", "")
                    .Add("QE_DatabaseType", "")
                    .Add("QE_LogonProperties", CRLogonProperties)
                    .Add("QE_ServerDescription", Me.XMLDataSource.Name.Substring(0, Me.XMLDataSource.Name.Length - Me.XMLDataSource.Extension.Length))
                    .Add("QE_SQLDB", "False")
                    .Add("SSO Enabled", "False")
                End With

                With CRConnectionDetails
                    .Attributes = CRDatabaseAttributes
                    .Kind = CrystalDecisions.ReportAppServer.DataDefModel.CrConnectionInfoKindEnum.crConnectionInfoKindCRQE
                    .UserName = ""
                    .Password = ""
                End With

                For I As Integer = 0 To XMLData.Tables.Count - 1
                    CRTable = New CrystalDecisions.ReportAppServer.DataDefModel.Table

                    With CRTable
                        .ConnectionInfo = CRConnectionDetails
                        .Name = XMLData.Tables(I).TableName
                        .QualifiedName = XMLData.Tables(I).TableName
                        .[Alias] = XMLData.Tables(I).TableName
                    End With

                    Me.CrystalReport.ReportClientDocument.DatabaseController.SetTableLocation(CRTables(I), CRTable)
                Next I
            End If
        End If
    End Sub
#End Region
End Class

参数对象(CRParameter

#Region "CRYSTAL REPORTS PARAMETER CLASS"
Public Class CRParameter
    Public Property ParameterName As String
    Public Property ParameterValue As Object

    Public Sub New(ByVal CurrentParameterName As String, ByVal CurrentParameterValue As Object)
        Me.ParameterName = CurrentParameterName
        Me.ParameterValue = CurrentParameterValue
    End Sub

    Friend Sub UpdateReportParameter(ByRef CurrentReport As CrystalDecisions.CrystalReports.Engine.ReportDocument)
        If Not CurrentReport Is Nothing Then
            If Not String.IsNullOrEmpty(Me.ParameterName) Then
                Using ReportFieldDefinitions As ParameterFieldDefinitions = CurrentReport.DataDefinition.ParameterFields
                    Using ReportParameter As ParameterFieldDefinition = ReportFieldDefinitions.Item(Me.ParameterName)
                        Dim ReportValues As ParameterValues = ReportParameter.CurrentValues
                        Dim NewValue As New ParameterDiscreteValue

                        ReportValues.Clear()
                        NewValue.Description = Me.ParameterName
                        NewValue.Value = Me.ParameterValue
                        ReportValues.Add(NewValue)
                        ReportParameter.ApplyCurrentValues(ReportValues)
                        ReportParameter.ApplyDefaultValues(ReportValues)
                    End Using
                End Using
            End If
        End If
    End Sub
End Class
#End Region

公式对象 (CRFormula)

#Region "CRYSTAL REPORTS FORMULA VALUE CLASS"
Public Class CRFormula
    Public Property FormulaName As String
    Public Property FormulaValue As Object

    Public Sub New(ByVal NewFormulaName As String, ByVal NewFormulaValue As Object)
        Me.FormulaName = NewFormulaName
        Me.FormulaValue = NewFormulaValue
    End Sub

    Friend Sub UpdateFormulaField(ByRef CurrentReport As CrystalDecisions.CrystalReports.Engine.ReportDocument)
        If Not CurrentReport Is Nothing Then
            If Not String.IsNullOrEmpty(Me.FormulaName) Then
                Try
                    If Me.FormulaValue Is Nothing Then
                        Me.FormulaValue = ""
                        CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                    ElseIf TypeOf Me.FormulaValue Is String AndAlso String.IsNullOrEmpty(Convert.ToString(Me.FormulaValue)) Then
                        Me.FormulaValue = ""
                        CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                    ElseIf TypeOf Me.FormulaValue Is String AndAlso Not String.IsNullOrEmpty(Convert.ToString(Me.FormulaValue)) Then
                        Me.FormulaValue = "'" & Me.FormulaValue.ToString & "'"
                        CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                    ElseIf TypeOf Me.FormulaValue Is Date Then
                        Me.FormulaValue = "'" & Convert.ToDateTime(Me.FormulaValue).ToString("yyyy-MM-dd") & "'"
                        CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                    Else
                        CurrentReport.DataDefinition.FormulaFields(Me.FormulaName).Text = Me.FormulaValue.ToString
                    End If
                Catch ex As Exception

                End Try
            End If
        End If
    End Sub
End Class
#End Region