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
对象,它可以重新实例化并传递一些只保留与报告的特定实例相关联的数据。我重构了 CRReport
、CRParameter
和 CRFormula
对象的代码,改为使用新的私有变量,一切看起来都完全符合预期。
这是修改后的完整代码
请记住,并非所有这些都经过全面测试。我还没有测试 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
我创建了一个库,试图处理在 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
对象,它可以重新实例化并传递一些只保留与报告的特定实例相关联的数据。我重构了 CRReport
、CRParameter
和 CRFormula
对象的代码,改为使用新的私有变量,一切看起来都完全符合预期。
这是修改后的完整代码
请记住,并非所有这些都经过全面测试。我还没有测试 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