Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal...

41
Beyond the Basics: Advanced use of Crystal Reports for Visual Studio .NET’s feature set and object model Written by Dan Burman This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies: Crystal Reports for Visual Studio.NET 2003, Visual Studio .NET 2003, SQL Server 7 or 2000, and Visual Basic .NET This article discusses: Exploration of the Crystal Reports object model Manipulation of reports’ visual formats during run-time through the ReportDefinition object Dynamic grouping, sorting, and field manipulation of Crystal Report documents during run-time through the DataDefinition object Data caching and exporting Deployment Dan Burman is a consultant for Progressive Systems Consulting, a New York-based custom software firm, specializing in .NET and SQL Server. You can contact him at [email protected]. Visit Progressive on the Web at http://www.progsys.com . Introduction You probably already know that Crystal Reports for Visual Studio .NET 2003 (CRVS) lets you create basic tabular and graphical reports quickly and easily with minimal programming and very little training. But careful study of the CRVS object model and namespaces can help you utilize advanced features of the CRVS reporting engine and provide extremely powerful dynamic control of report formatting and content at run time. While creating standard reports quickly is an obvious asset of CRVS, the fact that its object model allows a high-level of run time customization by you and/or your users is what makes it truly stand out. Using CRVS visually is easy and productive; but programming CRVS’s object model brings a new dimension to reporting, allowing for visual changes, ad-hoc data delivery, better performance, and the ability to pump data into other applications for further analysis and manipulation. The object model gives developers the opportunity to programmatically access report objects, report options, and the underlying data sources that feed the reports. Having a strong understanding of the objects that are the key players in constructing a report can provide developers the flexibility to manage and manipulate served reports by supporting runtime functionality, such as dynamic regrouping and resorting of data, recalculating data, and manipulating the report’s appearance. This article is a follow up to Add Professional Quality Reports to Your Application with Visual Studio .NET by Andrew Brust which appeared in the May 2002 edition of

Transcript of Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal...

Page 1: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Beyond the Basics: Advanced use of Crystal Reports for Visual Studio .NET’s feature set and object model Written by Dan Burman This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies: Crystal Reports for Visual Studio.NET 2003, Visual Studio .NET 2003, SQL Server 7 or 2000, and Visual Basic .NET

This article discusses: Exploration of the Crystal Reports object model Manipulation of reports’ visual formats during run-time through the

ReportDefinition object Dynamic grouping, sorting, and field manipulation of Crystal Report documents

during run-time through the DataDefinition object Data caching and exporting Deployment

Dan Burman is a consultant for Progressive Systems Consulting, a New York-based custom software firm, specializing in .NET and SQL Server. You can contact him at [email protected]. Visit Progressive on the Web at http://www.progsys.com.

Introduction You probably already know that Crystal Reports for Visual Studio .NET 2003 (CRVS) lets you create basic tabular and graphical reports quickly and easily with minimal programming and very little training. But careful study of the CRVS object model and namespaces can help you utilize advanced features of the CRVS reporting engine and provide extremely powerful dynamic control of report formatting and content at run time. While creating standard reports quickly is an obvious asset of CRVS, the fact that its object model allows a high-level of run time customization by you and/or your users is what makes it truly stand out. Using CRVS visually is easy and productive; but programming CRVS’s object model brings a new dimension to reporting, allowing for visual changes, ad-hoc data delivery, better performance, and the ability to pump data into other applications for further analysis and manipulation. The object model gives developers the opportunity to programmatically access report objects, report options, and the underlying data sources that feed the reports. Having a strong understanding of the objects that are the key players in constructing a report can provide developers the flexibility to manage and manipulate served reports by supporting runtime functionality, such as dynamic regrouping and resorting of data, recalculating data, and manipulating the report’s appearance. This article is a follow up to Add Professional Quality Reports to Your Application with Visual Studio .NET by Andrew Brust which appeared in the May 2002 edition of

Page 2: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

MSDN Magazine. In this article we will dive into the object model and look at how to utilize various objects to improve performance and create flexibility. We will build a series of Windows and Web forms that will load Crystal Reports running off of the SQL Server Northwind database, and manipulate their structures and appearance by accessing their object models.

Overview of object model At first, trying to make sense of the various CRVS assemblies, namespaces and objects can be a daunting task. Focusing on a few specific objects will help orient us through the entire object model and will be especially helpful in leveraging CRVS, to give us the most flexible reporting features possible. We’ll look at representative objects relevant to both report engine functionality as well as client-/viewer-side features. The three namespaces that we’ll focus on are:

• CrystalDecisions.CrystalReports.Engine – this namespace provides support for the report engine. It gives us access to all of the major reporting objects such as database fields, groupings, the underlying data structure, as well as reporting, printing, and exporting options

• CrystalDecisions.Windows.Forms - this namespace provides support for the Windows Forms Viewer control and associated classes. It gives us control over the Crystal Reports viewer control, enabling us to hide or show buttons, and print and export reports

• CrystalDecisions.Web - this namespace provides support for the Crystal Reports Web Viewer control and its associated classes

We will look into each of these namespaces to see how we can improve the customization of our reports using them.

Windows Forms Viewer Control The Windows Form Viewer Control can be found in the CrystalDecisions.Windows.Forms namespace. Crystal Reports allow developers to customize the look and behavior of the Crystal Reports Windows Forms Viewer control through a series of methods and properties. Below is a list of the more significant properties and methods that the control exposes to developers. Method Name Method Description PrintReport Prints the current report ExportReport Exports the current report RefreshReport Refreshes the current report ShowFirstPage Navigates the user to the first page of the report ShowLastPage Navigates the user to the last page of the report ShowNextPage Navigates the user to the next page of the report ShowNthPage Navigates the user to the specified page number. This

method requires that a page number in the form an integer be supplied.

ShowPreviousPage Navigates the user to the previous page of the report

Page 3: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Zoom Adjusts the zoom level of the current report. This method requires that a zoom level in the form of an integer be supplied.

Property Name Property Description ReportSource Stores the ReportDocument object being displayed.

This property accepts a string value. ShowCloseButton Shows or hides the close button on the viewer control.

This property accepts a Boolean value. ShowExportButton Shows or hides the export button on the viewer control.

This property accepts a Boolean value. ShowGotoPageButton Shows or hides the Go To page button on the viewer

control. This property accepts a Boolean value. ShowGroupTree Shows the group tree control ShowGroupTreeButton Shows or hides the group tree button on the viewer

control. This property accepts a Boolean value. ShowPageNavigateButtons Shows or hides the page navigation buttons on the

viewer control. This property accepts a Boolean value. ShowPrintButton Shows or hides the print button on the viewer control.

This property accepts a Boolean value. ShowRefreshButton Shows or hides the refresh button on the viewer control.

This property accepts a Boolean value. ShowTextSearchButton Shows or hides the text search button on the viewer

control. This property accepts a Boolean value. These exposed methods and properties give the developer considerable control over customizing the functionality that is enabled to the end user. It is possible to implement scenarios where, for example, only users of a certain security-level would have access to printing or exporting capabilities. This would be achieved through programmatic hiding and showing of the corresponding viewer buttons. In addition, the exposure of printing, exporting, and navigation methods opens up the possibility of creating custom buttons in you application to give the viewer an entirely different look. Figure 1 illustrates the key methods and properties that are available to the Windows Forms Viewer control.

Page 4: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

CrystalDecisions.Windows.Forms.CrystalReportViewer Object

ShowRefreshButton ShowPrintButton

ShowPageNavigateButtons ShowGroupTreeButton

ShowGotoPageButton ShowExportButton ShowCloseButton

ShowGroupTree

ShowTextSearchButton

Viewer Appearance Properties

ShowPreviousPage

ShowNthPageShowNextPageShowLastPageShowFirstPage

Viewer Navigation Methods

RefreshReport

Zoom

ExportReportPrintReport

Viewer Report Methods

ReportSource

Viewer Properties

Figure 1Key methods and properties of the Crystal Reports Windows Viewer control

CrystalDecisions.CrystalReports.Engine The CrystalDecisions.CrystalReports.Engine namespace is probably the most important namespace involved in programmatically accessing and modifying reports at runtime. The reason that this namespace is so important is that it contains the ReportDocument object. The ReportDocument is the top level CRVS object pertaining to the content and attributes of a given report at run-time, containing all the properties and methods needed to interface with and customize a report.

To use the ReportDocument you must first reference the CrystalDecisions.CrystalReports.Engine.dll assembly. As a convenience, this assembly reference, and others, are automatically added to your Visual Studio project when you add a Crystal Report rpt file to it.

Once the reference is in place, to get started, you can either use the ReportDocument’s Load method to open a physical .RPT report file, or use a strongly typed report class, which inherits from the ReportDocument class and is inextricably linked to a specific .rpt file, implicitly loading it upon instantiation.

ReportDocument Objects and Collections

The ReportDocument contains many properties and child objects that allow you to customize and control the look, feel, content, and data organization of your report. The main objects and collections of the ReportDocument are outlined below. Figure 2 illustrates the entire ReportDocument object model.

Page 5: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Database

The Database object provides access to the database information for all data sources contained in the report. The Database object contains a Tables collection, which in turn provides detailed information on each table used in the report. The Table object enables you to get or set the connection information through the various properties of its ConnectionInfo object. The Table object also provides information on each field in the table through its DatabaseFieldDefinitions collection.

DataDefinition

The DataDefinition object provides access to all database fields, parameter fields, sort fields, group-name fields, summary fields, running total fields, and SQL expression fields in the report. This object also provides access to the groups that are used in the report.

ExportOptions

The ExportOptions object provides properties for retrieving and setting options for exporting your report. The ExportOptions are used to set the destination options (disk, email, etc.), and the export type (Excel, PDF, Word, and HTML).

PrintOptions

The PrintOptions object provides properties and methods for setting the options for printing a report. Some of the properties that can be set are the printer name, paper size, paper orientation, and page margins.

ReportDefinition

The ReportDefinition object allows you to retrieve all of the Areas, ReportObjects, and Sections in a report. This enables you to get and set the formatting options for these items so the appearance of the report can be customized.

ReportOptions

The ReportOptions object allows you to get and set the data related to the report options (EnableSaveDataWithReport, EnableSavePreviewPicture, EnableSaveSummariesWithReport, and EnableUseDummyData) in a report.

Exploring the ReportDocument object a bit more will show us how to use some powerful run time reporting capabilities. To do this, we will create different reports which will exploit several of the objects listed above. We will be constructing a generic report that allows the developer to dynamically change its data source at runtime, and then access the report’s ReportObjects to customize the report based on its data source. We will also construct a report that will allow us to dynamically reorganize the data structure, by changing sort order, groupings, and recalculations of summary fields.

Page 6: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

SummaryInfo

The SummaryInfo object allows you to get and set summary information contained in the report. These fields can be set at design time in a report by right clicking on the design view and selecting Report Summary Info. This summary info is drawn on by some of the Special Fields objects such as File Author and Report Comments. This object exposes the KeywordsInReport, ReportAuthor, ReportComments, ReportSubject, and ReportTitle properties.

Page 7: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:
Page 8: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 2 Diagram of ReportDocument object model

“Ad Hoc-like” generic reports Once you have a basic understanding of the ReportDocument’s subsidiary object model, you can create some interesting and flexible reports. Let’s put some of our new found knowledge to work. In our first example, we’ll create a “skeletal” Crystal report, and take advantage of the features of the Database and ReportDefinition objects to supply the report with database, table and field specifics at run-time. This report will be architected in a generic fashion, so as to accommodate virtually any data source. We’ll also access the report’s ReportDefinition object to change the text labels in the report at run time based on the fields we choose to display. In the following code samples, we will construct a simple report (crReportDefinition.rpt) based off an actual data source with a particular structure. We will then create several database views that return data in a matching schema (i.e with the same number of columns and same column names). Note: fully built versions of all the reports we’ll be using throughout this article are supplied with the sample code. You can either build the report from scratch by following the step-by-step process, or you can use the pre-built versions and just read along for details of how each was constructed. In addition, we have supplied a login form called frmLogin.vb in the supporting code samples. This form collects the login credentials for the user along with the server name and the database name of the database that you’ll be running the reports against. While we won’t go into detail about this form, be aware that it’s setting various properties in a cLoginCredentials shared class. These properties will be accessed from our samples, and we’ll point out where this occurs. Creating our views in SQL Server We will be creating a few views in SQL Server to run our report against. It should be noted that the same techniques could be used against other databases that support views, including Access and Oracle. Within the Server Explorer window of Visual Studio, let’s add three new views to the Northwind database. The database views in Listing 1, are the three views that we’ll be creating to provide data to our generic report “skeleton”. You can enter them manually, or use a database script (DatabaseViews.sql) that has been provided with the accompanying code samples.

Page 9: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

CREATE VIEW GenericCustomers_vw AS SELECT [CustomerID] AS Field1, [CompanyName] AS Field2, ISNULL([Address], '') + ' ' + ISNULL([City], '') + ', ' + ISNULL( [Region], '') + ' ' + ISNULL( [PostalCode], '') AS Field3, [Phone] AS Field4 FROM Customers ----------------------------------------------------------------------------------- CREATE VIEW GenericEmployees_vw AS SELECT [EmployeeID] AS Field1, [LastName] + ', ' + [FirstName] AS Field2, ISNULL([Address], '') + ' ' + ISNULL([City], '') + ', ' + ISNULL( [Region], '') + ' ' +ISNULL( [PostalCode], '') AS Field3, [HomePhone] AS Field4 FROM Employees --------------------------------------------------------------------------------------- CREATE VIEW GenericProducts_vw AS SELECT [ProductID] AS Field1, [ProductName] AS Field2, [CategoryName] AS Field3, [UnitPrice] AS Field4 FROM Products p INNER JOIN Categories c ON p.CategoryID = c.CategoryID Listing 1 SQL views to provide data to generic reports. For each of these views, notice that the fields will all be output as generic column names: “Field1”, “Field2”, “Field3”, & “Field4”. As long as the views all conform to the same schema, we will be able to use them interchangeably in our reports. We’ll get to the specifics of how this all works in just a moment. In addition, notice in the GenericProducts_vw view, we are joining the Products and Categories tables. So it is possible to incorporate any number of database tables into this generic reporting model, as long as all of the schemas conform to each other. Now that we’ve created our views, let’s build our report.

Page 10: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Constructing the report Open a new VB Windows Forms project in Visual Studio, and add a Crystal Report to it named “crReportDefinition.rpt”. Using the standard Report Expert to set up our report, select GenericEmployees_vw as the data source. Generate a standard tabular report based on the selected data source (no grouping is necessary). Now that the basic report set up is complete, let’s make some minor modifications to our report objects. In the property window, rename each of the four column header labels that appear in the Page Header section of the report from “Text1”, “Text2”, “Text3”, & “Text4” to “FieldHeader1”, “FieldHeader2”, “FieldHeader3”, and “FieldHeader4” respectively. Also rename the section names to “ReportHeaderSection”, “PageHeaderSection”, “DetailsSection”, “ReportFooterSection”, and “PageFooterSection” respectively. Next, add a text object to the Page Header section. To achieve this, right click on the Page Header section and select Insert Text Object. Rename the text object “txtReportTitle” and type in “Report Title” into the object. The report should now appear similar to the report illustrated in Figure 3.

Figure 3 Design view of crReportDefinition.rpt

Page 11: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Constructing the form Now that we’ve constructed our report, let’s add a new form to our project called “frmReportDefinition.vb”. See Figure 4 and consult the sample code project accompanying this article to see how to set up the form.

Figure 4 Design view of frmReportDefinition.vb When the user selects a data source from the combo box and clicks the Load Data Source button, our code will access the ReportDocument object’s Database object to dynamically change the report’s data source on the button’s click event through its SetDataSource method. Listing 2 shows the code that handles the loading of a data table based on our data source that we select, and then the application of that data table to the report’s data source. Private Sub cmdDataSource_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdDataSource.Click ' Loads a data table based on a view name selected ' in the cboDataSource combo box and then sets the ' report document's data source to that data table If Not lstDataSource.SelectedItem Is Nothing Then rdReportDefinition.Database.Tables(0).SetDataSource _ (LoadDataTable(lstDataSource.SelectedItem)) CheckColumnAlignment()

Page 12: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

' Refreshes report to see the changes crvReportDefinition.RefreshReport() Else MsgBox("Please select a data source.") End If End Sub Private Function LoadDataTable(ByVal sViewName As String) As DataTable ' Builds the SQL statement that we'll be running to load ' the data set Dim sSQLStatement As String = "Select * FROM " & sViewName Dim daGeneric As SqlDataAdapter Dim dtGeneric As DataTable ' Instantiates a new data adapter based on the SQL ' statement that is passed in. daGeneric = New SqlClient.SqlDataAdapter(sSQLStatement, _ New SqlClient.SqlConnection( _ cLoginCredentials.ConnectionString)) Try With daGeneric .SelectCommand.CommandType = CommandType.Text dtGeneric = New DataTable ' fills the data set with the returned records .Fill(dtGeneric) End With Return dtGeneric Catch ex As Exception ' display message the the load failed MsgBox("There was a problem loading the data.") End Try End Function Private Sub CheckColumnAlignment() Dim roDetails As CrystalDecisions.CrystalReports.Engine.ReportObject Dim iFieldCounter As Int16 = 1 Dim sColumnHeaderName As String = "" ' loops through each report object in the "DetailsSection" section of ' the report. For Each roDetails In _ rdReportDefinition.ReportDefinition.Sections( _ "DetailsSection").ReportObjects ' Builds the string for the header column text sColumnHeaderName = "FieldHeader" & iFieldCounter.ToString ' If the field object is is a number field then right align its

Page 13: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

' corresponding column header. Otherwise, left align its column ' header If IsNumeric("Field" & iFieldCounter.ToString) Then GetColumnHeaderObjectFormat( _ sColumnHeaderName).HorizontalAlignment = _ CrystalDecisions.[Shared].Alignment.RightAlign Else GetColumnHeaderObjectFormat( _ sColumnHeaderName).HorizontalAlignment = _ CrystalDecisions.[Shared].Alignment.LeftAlign End If 'Increments the counter by 1 iFieldCounter += 1 Next End Sub Private Function IsNumeric(ByVal sFieldName As String) As Boolean ' If the value type of the field name that is passed in is one ' of the mathcing numeric types, then return True. Otherwise, ' return False. Select Case _ ConvertCrystalFieldObject(sFieldName).DataSource.ValueType Case CrystalDecisions.[Shared].FieldValueType.CurrencyField, _ CrystalDecisions.[Shared].FieldValueType.Int16sField, _ CrystalDecisions.[Shared].FieldValueType.Int16uField, _ CrystalDecisions.[Shared].FieldValueType.Int32sField, _ CrystalDecisions.[Shared].FieldValueType.Int32uField, _ CrystalDecisions.[Shared].FieldValueType.Int8sField, _ CrystalDecisions.[Shared].FieldValueType.Int8uField, _ CrystalDecisions.[Shared].FieldValueType.NumberField Return True Case Else Return False End Select End Function Private Function GetColumnHeaderObjectFormat( _ ByVal sColumnHeaderName As String) _ As CrystalDecisions.CrystalReports.Engine.ObjectFormat ' Returns the ObjectFormat object corresponding to the name ' that's passed in. Return ConvertCrystalTextObject(sColumnHeaderName).ObjectFormat End Function Listing 2 Code to load new data source into report When the user clicks the Load Data Source button, the event retrieves a DataTable populated with the results of the view select statement that was selected from our data source combo box. Then it accesses the Tables collection of the ReportDocument’s Database object and calls its SetDataSource method, passing it the returned DataTable. The result is that the report will load the generic DataTable into our report “skeleton” and the report will take on a totally different meaning based on the data it displays.

Page 14: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

One issue with regards to the this generic reporting model to note is that the FieldObject object’s text alignment is managed by Crystal Reports, and is interpreted based on the underlying field’s data type. So if the field that is returned is a numeric type, the field’s data is automatically right-aligned. This is very convenient; however, what the report fails to do is align the corresponding column header to match its data field’s alignment. The result is that a numeric field will be right aligned but its header will be left aligned. We’ll need to programmatically control the alignment of these column header based on the data type of their details. In the CheckColumnAlignment method, we loop through each of the columns in the details section of the report and check if it’s a number field. If it is, then we right align the associated column header. Otherwise, we left align it. While this technique allows us to create a single report that can display data from any data source with a compatible schema, if we do no additional work, each report will still have generic look to it. The column header labels and the report titles will all be the same, regardless of the data used. Finding a solution this problem is where the ReportDefinition object comes in. By accessing its ReportObjects collection, we can reference any object of the ReportDocument, such as a TextObject, directly, and modify its properties. Private Sub cmdUpdateReportFields_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles cmdUpdateReportFields.Click ' Loads the text box into the corresponding report field header 'texts() ConvertCrystalTextObject("FieldHeader1").Text = txtField1.Text ConvertCrystalTextObject("FieldHeader2").Text = txtField2.Text ConvertCrystalTextObject("FieldHeader3").Text = txtField3.Text ConvertCrystalTextObject("FieldHeader4").Text = txtField4.Text ConvertCrystalTextObject("txtReportTitle").Text = _ txtReportTitle.Text ' Refreshes the report so that you can see the changes. crvReportDefinition.RefreshReport() End Sub Private Function ConvertCrystalTextObject( _ ByVal sTextObjectName As String) _ As CrystalDecisions.CrystalReports.Engine.TextObject ' Takes in the name of a Crystal TextObject as a string and ' return the TextObject Return CType(rdReportDefinition.ReportDefinition.ReportObjects _ (sTextObjectName), _ CrystalDecisions.CrystalReports.Engine.TextObject) End Function Listing 3 Code to load text values into the corresponding report object’s text property In the cmdUpdateReportFields.Click event (Listing 3), we’re setting the specific report objects’ (the column headers and report title) text properties to the values

Page 15: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

entered in the text boxes on frmReportDefinition. By accessing the ReportObjects’ ObjectFormat property, you can also manipulate the report object’s ability to grow in size when the field’s content exceeds its length, and change its text alignment, etc. This enables you to customize the report further. The implications of creating a generic report are fairly grand. In a production environment, where the code is already compiled and running on the client machines, a developer can simply add a new view to the database, and if it follows the data structure expected by the report, the report will be able to display it without recompilation. In our sample, we’ve loaded our various data sources from a config file. If a new view is added to the database, the developer only needs to add that view name into the config file to allow the application to access this new view. This technique can save the trouble of deployment issues, because all reports are being served from a central data source. There is no re-installation of new reports required. This gives the application users the flexibility to select the data that they want to see and how they want to see it, without having to write the underlying code to format and render the reports.

Figure 5 Runtime view of crReportDefinition.rpt populated with the product view

Figure 5 depicts our “skeleton” report with GenericProducts_vw as its data source. Figure 6 depicts our “skeleton” report with GenericCustomers_vw as its data source. Figure 7 depicts our “skeleton” report with GenericEmployees_vw as its data source. As you can see the three different data sources yield consistent-looking reports.

Page 16: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Note that the fourth column header for GenericProducts_vw is right aligned, while the other views are left aligned. This is our CheckColumnAlignment method hard at work.

Figure 6 Runtime view of crReportDefinition.rpt populated with the customer view

Page 17: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 7 Runtime view of crReportDefinition.rpt populated with the employee view Grouping, Sorting and Recalculating Reports The Crystal Reports object model also allows developers to have programmatic control during run time of the data structure of a report. The DataDefinition object provides developers access to the underlying data structure of a report, granting programmatic control of record grouping, column sorting, and the ability to manipulate formula fields throughout the report. To gain access to the DataDefinition object, you’ll need a reference to the CrystalDecisions.CrystalReports.Engine namespace. Dropping an instance of a ReportDocument component onto a form in the IDE of Visual Studio will automatically set up that reference and make its objects available for programmatic manipulation. Let’s explore this in another example. We’ll create a calculated order details report on the Northwind database that will be able to dynamically calculate an extended price, optionally applying the discount in the database, according to the user’s run time request. We’ll also be able to regroup the records and sort on any column. Constructing the report Let’s begin this example by adding a new Crystal Report t0 our VB WinForms project and naming it “crDataDefinition.rpt”. Using the standard Report Expert, set the

Page 18: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

report data source to the Order_Details_Extended view of the Northwind database. This view is a supplied with the database (so no script execution is necessary) that displays a list of order details records. Use the standard Report Expert to set up the report to be grouped on Order_Details_Extended.ProductName and to sort on Order_Details_Extended.OrderID. We’ll need to make some modifications to the report to meet our specific needs. Since we’re going to be interacting with the column fields, we should rename them to give each a more meaningful name. Rename the field objects in the Details section of the report to fldProductName, fldOrderID, fldUnitPrice, fldQuantity, and fldDiscount. Next, let’s add a field to hold the extended price. In the Field Explorer window, right click on Formula Fields, and select New, and supply the name ExtendedPrice. When the Formula Editor opens up, enter the formula in Listing 4.

({Order_Details_Extended.UnitPrice} * {Order_Details_Extended.Quantity})

Listing 4 Formula for calculating extended price without incorporating discount Save and close the Formula Editor. The formula will now appear in the Field Explorer under the Formula Fields node. Drag and drop the formula to the far right of the report in the Details section and rename the field fldExtended. Rename the column header for this formula field to lblRecordTotal. Next, right click on the report designer and select Insert Subtotal. The Insert Subtotal Expert will pop up. In the first field, select @ExtendedPrice to choose our formula field as the subtotaled field. In the second field, select Order_Details_Extended.ProductName to make sure the subtotal is calculated for each group. Click OK. Next, let’s add a grand total for the report. Right click again on the report designer and Insert Grand Total. This will cause the Grand Total Expert to pop up. Select Maximum of @ExtendedPrice. This tells the grand total field to calculate all of the @ExtendedPrice fields.

Page 19: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 8 Design view of crDataDefinition.rpt The report is now completed (and should look similar to the report depicted in Figure 8), and we are ready to build a form to access our report functionality. Constructing the form Now that we’ve constructed our report, let’s build a Windows form to view and manipulate this report. Add a new form to it call “frmDataDefinition.vb”. Consult the accompanying code project to see how to set up the Windows form. The form should be set up to look like Figure 9.

Page 20: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 9 Design view of frmDataDefinition.vb The first piece of functionality that we’ll implement is the dynamic grouping of database fields. Behind the Group Field button, we’ll add some code that will add the selected column to the Groups collection of the DataDefinition object.

Page 21: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Private Sub cmdGroup_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdGroup.Click If Not lstGroup.SelectedItem Is Nothing Then ' Groups the records on the field selected rdDataDefinition.DataDefinition.Groups(0).ConditionField = _ ConvertCrystalFieldObject(lstGroup.SelectedItem).DataSource ' Refresh the report crvDataDefinition.RefreshReport() End If End Sub Private Function ConvertCrystalFieldObject( _ ByVal sFieldObjectName As String) _ As CrystalDecisions.CrystalReports.Engine.FieldObject ' Takes in the name of a Crystal FieldObject as a string ' and return the FieldObject Return CType(rdDataDefinition.ReportDefinition.ReportObjects( _ sFieldObjectName), _ CrystalDecisions.CrystalReports.Engine.FieldObject) End Function

Listing 5 Code that dynamically regroups the report based on the select field Upon clicking on the Group Field button, the ConditionField property of the Groups collection of the DataDefinition object is set to the DataSource property of the selected field object. By setting the Groups collection to the underlying data field of the selected field object, we can dynamically change the report grouping at runtime. This provides the powerful ability of allowing the user to group the report in the way he/she desires (assuming the report form permits this functionality). In this particular example, we set the selected field object to the first indexed position of the Groups collection, Groups(0). This report only groups on one data field at a time. Theoretically, we could have added a member to the Groups collection to allow nested grouping. One very important thing to mention about the regrouping of these records is that we set the report up to run sub totals and grand totals based on the report groupings. So when we regroup our records, the sub totals and grand totals are recalculated based on the grouping organization. In other words we are creating dynamic reports that can be regrouped, with line items subtotaled based on the new grouping. Next, we want to handle the sorting functionality of the report. The DataDefinition object allows developers to sort on any report column, either ascending or descending fashion, at runtime. In the code behind the Sort Field button, let’s add some logic that will add a database field’s data source to the SortFields collection of the DataDefinition object.

Page 22: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Private Sub cmdSort_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdSort.Click If Not lstSort.SelectedItem Is Nothing Then ' Sets the sort column to the field selected SortColumn(lstSort.SelectedItem) End If End Sub Private Sub SortColumn(ByVal sFieldName As String) With rdDataDefinition.DataDefinition.SortFields(1) ' If the sort field hasn't changed then change the sort order ' otherwise, set the new field value and set its sort order to ' ascending. We do this to ensure that the sort order always ' starts with ascending when we change the sort field If .Field.Name = _ ConvertCrystalFieldObject(sFieldName).DataSource.Name Then ' If the sorting column is sorted ascending, ' then sort it descending and vice versa If .SortDirection = _ CrystalDecisions.[Shared].SortDirection.DescendingOrder Then .SortDirection = _ CrystalDecisions.[Shared].SortDirection.AscendingOrder Else .SortDirection = _ CrystalDecisions.[Shared].SortDirection.DescendingOrder End If Else ' Sets the sort column to the field passed in .Field = ConvertCrystalFieldObject(sFieldName).DataSource ' Sets the sort column's sort order .SortDirection = _ CrystalDecisions.[Shared].SortDirection.AscendingOrder End If End With ' Refresh the report crvDataDefinition.RefreshReport() End Sub Listing 6 Code that dynamically sorts the report based on the select field When the user clicks the Sort Field button, the event passes in the name of the report field selected in the list box to the SortColumn method. The SortColumn method in turn will process the sorting of that field. The Field property of the DataDefininition’s SortFields collection accepts a Field object’s DataSource. It is setting this Field property that determines which column will be sorted. The SortDirection property of the DataDefininition’s SortFields collection determines the direction of the sorted field and it can either be ascending or descending. The final piece of functionality that we’ll add to this form is the ability to recalculate the extended price on the fly, either including or excluding the discount in the calculation. In the code behind the Recalculate Price check box, we’ll add code that assigns the Text property of the DataDefinition’s FormulaFields collection to a formula in the form of a string. Private Sub chkRecalculatePrice_CheckedChanged( _

Page 23: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles chkRecalculatePrice.CheckedChanged With rdDataDefinition If chkRecalculatePrice.Checked Then ' sets the formula field in the generic report to the ' formula that includes the discount in the calculation of ' its total .DataDefinition.FormulaFields("ExtendedPrice").Text = _ "({Order_Details_Extended.UnitPrice} * " & _ "{Order_Details_Extended.Quantity}) * " & _ "(1 - {Order_Details_Extended.Discount})" ' Sets the column header text ConvertCrystalTextObject("lblRecordTotal").Text() = _ "Total with Discount" Else ' sets the formula field in the report to the ' formula that excludes the discount in the calculation of ' its total .DataDefinition.FormulaFields("ExtendedPrice").Text = _ "({Order_Details_Extended.UnitPrice} * " & _ "{Order_Details_Extended.Quantity})" ' Sets the column header text ConvertCrystalTextObject("lblRecordTotal").Text() = _ "Total without Discount" End If End With ' Refreshes the report crvDataDefinition.RefreshReport() End Sub Listing 7 Code that dynamically recalculates the extended price If the user had checked the check box, the Text property of the “ExtendedPrice” item within the FormulaFields collection is set to a formula that includes the discount column into the calculations of the extended price. If the check box is unchecked, the discount field is excluded from being factored into the calculation of the extended price. In building this report, we looked at how to programmatically access the grouping, sorting, and formula fields of the underlying data definitions of a Crystal report. The ability to manipulate the structure and calculations of data within a report provides developers another technique for implementing ad hoc reporting functionality. Providing users the ability to regroup, re-sort and recalculate reports can enhance usability, and ease the demands of report development. If users need to reorganize an existing report, so that it highlights a different end result, developers are no longer required to create a specific report to meet the end user’s needs. Instead, one flexible, well-designed report can provide users all the flexibility to reorganize the reports themselves.

Page 24: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

In addition to the deployment advantages of creating reports like these, there are performance advantages to boot. By directly accessing the ReportDocument’s object model, we avoid the need to serve up another report. While there is some processing time required to reformat the data through the object model, the server isn’t required to regenerate a new report from scratch. The larger and more complex a report is, the more valuable this performance enhancement will be . Advanced Exporting One of the most useful features offered by Crystal Reports is its exporting feature set. Crystal Reports permits users to export their reports from the viewer into an array of formats (such as rich text, Excel spreadsheets, and PDF documents ) so they can manipulate and analyze the underlying data as they see fit or publish the report in formats readable by virtually any computer user. In addition to the report viewer’s export button, the ReportDocument object exposes three different exporting methods to developers, so run time, programmatic exporting can be exploited. The three methods are:

• Export - Exports a report to a format and destination specified within the ExportOptions Object. The ExportOptions object permits the manual configuration of the file format and destination to allow greater control over the environment.

• ExportToDisk - Exports the report to a file in the specified format (useful in smart client applications).

• ExportToStream - Exports the report to an in-memory stream in the specified format (very useful for Web-based rendering of reports in foreign formats without creating intermediate disk files on the Web server). ExportToStream will be covered in the Web exporting section.

Let’s create another form to see these export capabilities in action. Add a new form to the project and call it frmExport.vb. It should look like as depicted in Figure 10 at design time.

Page 25: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 10 Design view of frmExport.vb

Behind the Export Options buttons, place the following code:

Page 26: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Private Sub cmdExport_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdExport.Click ' Declare variables and get the export options. Dim eoExportOptions As New ExportOptions Dim dfdoDiskOptions As New DiskFileDestinationOptions With rdDataDefinition eoExportOptions = .ExportOptions With eoExportOptions ' Set the export format. .ExportFormatType = _ ExportFormatType.RichText .ExportDestinationType = _ ExportDestinationType.DiskFile ' Set the disk file options. dfdoDiskOptions.DiskFileName = _ Application.StartupPath & "\rdDataDefinition2.rtf" .DestinationOptions = dfdoDiskOptions End With ' Export the report. .Export() End With End Sub Private Sub cmdExportToDisk_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdExportToDisk.Click rdDataDefinition.ExportToDisk(ExportFormatType.RichText, _ Application.StartupPath & "\rdDataDefinition.rtf") End Sub

Listing 8 Code to export reports to various media The click event of the Export to Disk button simply calls the ReportDocument object’s ExportToDisk method and passes in a document format type and a destination file name. Upon clicking this button, the report will be exported in Rich Text format, and it will be saved as the destination file name in the project’s bin folder. The click event of the Export button sets up the ReportDocument objects ExportOptions object. The ExportOptions object provides properties for retrieving and setting options for exporting reports. Within the export options we set the ExportFormatType to RichText and the ExportDestinationType to DiskFile. We set the DiskFileDestinationOptions objects DiskFileName property to the destination file name. Once all of the export options are defined, we simply need to call the ReportDocument object’s Export method. Upon clicking this button, the report will be exported in Rich Text format, and it will be saved as the destination file name as defined in the DiskFileDestinationOptions object. The end result is that we manually configured the export options to replicate the functionality of the ExportToDisk method. CrystalDecisions.Web

Page 27: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

CrystalDecisions.Web namespace provides support for the Crystal Reports Web Viewer control, and its associated classes. We will take a brief look at the Web Forms Report Viewer control, and then explore some of the functionality that is supported in exclusively Web-based reports, through a series of samples. Like the Windows-based samples, we’ve created a login form (frmLogin.aspx) for the Web samples as well, and placed it in the sample application. This form collects the user’s login credentials, as well as a server and database name of the database that the reports will run against. While the details of this form aren’t terribly relevant to the topic, we’ll be storing the various user credentials into session variables from the login form. These session variables will then be referenced in our various Web samples. Crystal Reports Web Viewer control To use the Crystal Reports Web Viewer control, your project must reference the CrystalDecisions.Web.dll assembly. For the convenience of the developer, the reference to this assembly is added to the project the moment that the Crystal Reports Web Viewer control is dropped onto a Web Form. Much like the Windows Forms Viewer, its Web counterpart exposes a number of properties and methods, allowing the developer to programmatically navigate through the report, call report methods, set the report source, and hide or show various viewer controls such as buttons and the group tree. Below is a list of the more significant properties and methods that this control exposes to developers. Method Name Method Description RefreshReport Refreshes the current report ShowFirstPage Navigates the user to the first page of the report ShowGroupTree Shows the group tree control ShowLastPage Navigates the user to the last page of the report ShowNextPage Navigates the user to the next page of the report ShowNthPage Navigates the user to the specified page number. This

method requires that a page number in the form an integer be supplied.

ShowPreviousPage Navigates the user to the previous page of the report Zoom Adjusts the zoom level of the current report. This

method requires that a zoom level in the form of an integer be supplied.

Property Name Property Description DisplayGroupTree

Shows or hides the group tree on the viewer control. This property accepts a Boolean value.

DisplayToolbar Shows or hides the toolbar on the viewer control. This property accepts a Boolean value.

HasDrillUpButton Shows or hides the drill up button on the viewer control. This property accepts a Boolean value.

HasGotoPageButton Shows or hides the go to page button on the viewer control. This property accepts a Boolean value.

HasPageNavigationButtons Shows or hides the navigation button on the viewer control. This property accepts a Boolean value.

Page 28: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

HasRefreshButton Shows or hides the refresh button on the viewer control. This property accepts a Boolean value.

HasSearchButton Shows or hides the search button on the viewer control. This property accepts a Boolean value.

ReportSource Stores the ReportDocument object being displayed. This property accepts a string value.

Figure 11 illustrates the key methods and properties that are available to the Web Viewer control.

CrystalDecisions.Web.CrystalReportViewer Object

ShowPreviousPage

ShowNthPageShowNextPageShowLastPageShowFirstPage

Viewer Navigation Methods

RefreshReport

Zoom

Viewer Report Methods

ReportSource

Viewer Properties

DisplayGroupTree

HasPageNavigationButtonsHasGotoPageButton

DisplayToolbarHasDrillUpButton

HasSearchButtonHasRefreshButton

Figure 11 Key methods and properties of the Crystal Reports Web Viewer control Cascading Style Sheets in Web Reports Just as in Windows Forms reporting, Web Forms reporting enables us to access all of the objects of the ReportDocument component, enabling us to implement the same functionality as demonstrated previously. Because both Web Forms reporting and Windows Forms reporting reference the CrystalDecisions.crystalreports.Engine.dll assembly, they both access the core classes already discussed. One difference that does exist between Windows Forms reporting and Web Forms reporting is that Web Forms reporting supports cascading style sheets. Cascading style sheets are files that define a centralized set of style definitions that Web-based reports can draw on for aesthetic design. In Windows Forms reporting, in order to change the font color of a report object, you must either set the report object’s TextColor property at design time in the report itself, or programmatically access the report objects through the ReportDefinition object. If you want to change the appearance of a report based on a user, you must go through the somewhat tedious process of setting various objects’ properties. However, Web Reports work a little differently.

Page 29: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Because we’re working in the Web environment with ASP.NET/.aspx files, and because HTML-rendered Crystal Reports are CSS-aware, aesthetic manipulation of reports via style sheets is supported. This means we can create various style classes, and then apply those classes to individual report objects, report sections, or the entire report. Let’s see this in action. Creating Style Classes Create a new Visual Basic ASP.NET Web Application in Visual Studio. In the default Styles.css file, enter the following style classes. .GreenAndFuchsia { font-weight: 900; font-size: xx-small; word-spacing: normal; text-transform: none; color: fuchsia; font-family: Verdana, Arial, Helvetica, sans-serif; letter-spacing: normal; background-color: green; text-decoration: none; } .RedAndYellow { font-weight: 900; font-size:xx-small; word-spacing: normal; text-transform: none; color: yellow; font-family: Verdana, Arial, Helvetica, sans-serif; letter-spacing: normal; background-color: red; text-decoration: none; } .AquaAndMaroon { font-weight: 900; font-size:xx-small; word-spacing: normal; text-transform: none; color: Maroon; font-family: Verdana, Arial, Helvetica, sans-serif; letter-spacing: normal; background-color: aqua; text-decoration: none; } Listing 9 CCS Style class definitions in Styles.css We have just pre-defined three distinct style classes that we will use to dynamically change the appearance of our report at run time. Next, let’s add an existing report into our project, just so we can see the reports that we create in a Windows Forms environment will also work in a Web Forms

Page 30: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

environment. Add the previously created crDataDefinition.rpt report to the Web project. Now add a new Web Form, name it frmStyle.aspx, and drop a Crystal Reports Viewer control on it. In addition, add a couple of combo boxes, a button, and a ReportDocument component onto the form (see Figure 12). One combo box will contain a list of the style classes that we set up (GreenAndFuchsia, RedAndYellow, & AquaAndMaroon). The other combo box will contain a list of the various object scopes defined for a report. The CrystalDecisions.Shared.dll assembly provides access to the various object scopes for a report (such as ReportHeaderSections, ReportFooterSections, AllReportObjectsInReportHeaderSections, AllReportObjects etc.). When a ReportDocument object is dropped on a form, the reference to the CrystalDecisions.Shared.dll assembly is automatically added to the project. When the time comes to apply a style class to our report, we’ll need to specify the scope of our style application.

Figure 12 Design view of frmStyle.aspx

Behind the button, we’re going to be adding code that assigns a style class to the ReportDocument object through its SetCssClass method.

Page 31: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Private Sub btnApplyStyle_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnApplyStyle.Click ' Sets the CSS class to the object scope and the ' style class as a string crReportDocument.SetCssClass(GetObjectScope(), _ cboStyleClass.SelectedValue.ToString) End Sub Private Function GetObjectScope() _ As CrystalDecisions.[Shared].ObjectScope ' Take the string value of the selected combobox for the object ' scope and returns the ObjectScope object Select Case cboObjectScope.SelectedValue.ToString Case "ReportHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.ReportHeaderSections Case "ReportFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.ReportFooterSections Case "PageHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.PageHeaderSections Case "PageFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.PageFooterSections Case "GroupHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.GroupHeaderSections Case "GroupFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.GroupFooterSections Case "DetailSections" Return _ CrystalDecisions.[Shared].ObjectScope.DetailSections Case "AllSummaryObjects" Return _ CrystalDecisions.[Shared].ObjectScope.AllSummaryObjects Case "AllSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllSections Case "AllReportObjectsInReportHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInReportHeaderSections Case "AllReportObjectsInReportFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInReportFooterSections Case "AllReportObjectsInPageHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInPageHeaderSections Case "AllReportObjectsInPageFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInPageFooterSections Case "AllReportObjectsInGroupHeaderSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInGroupHeaderSections Case "AllReportObjectsInGroupFooterSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInGroupFooterSections Case "AllReportObjectsInDetailSections" Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjectsInDetailSections Case "AllReportObjects"

Page 32: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Return _ CrystalDecisions.[Shared].ObjectScope.AllReportObjects End Select End Function Listing 10 Code to dynamically set style class to a report object or section In the click event of our button, we call the SetCssClass method of the ReportDocument object and pass it the selected values of our two combo boxes. This method expects an ObjectScope object and a Css class name. To obtain an ObjectScope object, we have implemented a function called GetObjectScope, which takes the selected scope string and returns the corresponding object reference.

Figure 13 Browser view of report with the RedAndYellow class applied to the AllReportObjectsInDetailSections object scope When you run this form in the browser, the selected Css class name and the object scope will drive the look and breadth of the style application, In Figure 13, we have applied the RedAndYellow Css class to all of the report objects that appear in the Details section of our report. In Figure 14, we’re applying the AquaAndMaroon Css class to all of the report objects in the page header sections of our report.

Page 33: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Figure 14 Browser view of report with the AquaAndMaroon class applied to the AllReportObjectsInPageHeaderSections object scope

Using style sheet to format your Web-based reports can be a big time and money saver. If a need arises where you have a series of reports and you want to change the overall look of each report to give each a consistent look and feel (for example, applying a specific color scheme for a specific customer), you can make the change once in the style sheet, as opposed to making a change in each report individually. In addition, since the style formatting is in a .css file, we can make changes to a style without requiring a recompilation of the entire project. Data Caching Caching data can provide terrific performance gains when running Crystal Reports. Basically, caching offers a temporary in-memory storage of data so that it can be retrieved more quickly on subsequent requests. By storing data in a cache, we can minimize the number of requests that we make on our database to serve up new reports. Within the Crystal caching model, strongly-typed reports implement the ICachedReport interface provided by CrystalDecisions.ReportSource namespace. This interface provides access to a CreateReport method which creates an instance of a strongly-typed report, as well as provides access to the default cache key through the GetCustomizedCacheKey method. (For a more in depth sample of implementing the ICachedReport interface, refer to “Add Professional Quality Reports to Your

Page 34: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Application with Visual Studio .NET” by Andrew Brust in the May 2002 edition of MSDN Magazine.) The Crystal caching model is supplemented by .NET’s System.Web.Caching namespace to manage the caching and expiration of data. In our final example, we will explore the caching model by building a Web Form which will load data from the Northwind database or the cache depending on whether that cache contains data. We will customize the caching capabilities by comparing the data in the cache against that of the database to determine if the cached data is usable. Constructing the Form To begin, create a new Web form called frmCache.aspx. Drop a Crystal Report Viewer Control (crvDataCache), a couple of buttons (btnRefreshList & btnClearCache), a ReportDocument component (rdDataCache) and a couple of labels for caching and error messages onto the form (See Figure 15). Again, we will be using the crDataDefinition.rpt report to illustrate our caching example.

Figure 15 Design view of the caching form Next, we’ll need to add the code illustrated in Listing 11. Again, all the code has been supplied in the accompanying samples. Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load If Not IsPostBack Then

Page 35: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

LoadData() End If End Sub Sub RefreshBtn_Click(ByVal Src As Object, ByVal E As EventArgs) LoadData() End Sub Private Sub btnClearCache_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnClearCache.Click Cache.Remove("MyData") LoadData() End Sub Sub LoadData() With rdDataCache ' Loads the report into the Report Document control. ' The session variable is set in the frmLogin.aspx form .Load(Session("WebFolder") & "crDataDefinition.rpt") ' Sets the database login info. These session variables ' are set in the frmLogin.aspx form .SetDatabaseLogon(Session("UserName"), Session("Password")) ' loads the report document into the viewer crvDataCache.ReportSource = rdDataCache ' Check to see if there is a value in "MyData". If not, ' then the database must be requeried. If there is a ' value, the data from the cache is then checked to ' ensure that no records have been deleted or added. ' If there has been a change to the data, then the ' database is requeried. Otherwise, the cached copy is ' used. If Cache("MyData") Is Nothing Then ' Gets data from the database and puts it in the ' cache LoadDataCache() ' Sets the reports data source to the cached data set .SetDataSource(Cache("MyData")) ' Sets a data status message CacheMsg.Text = "Dataset created explicitly. " & _ "There was no data in the cache." Else If CachedRowCountEqualsDatabaseRowCount(Cache("MyData")) Then ' Sets the reports data source to the cached data set .SetDataSource(Cache("MyData")) ' Sets a data status message CacheMsg.Text = "Dataset retrieved from cache" Else

Page 36: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

' Gets data from the database and puts it in the ' cache LoadDataCache() ' Sets the reports data source to the cached data set .SetDataSource(Cache("MyData")) ' Sets a data status message CacheMsg.Text = "Dataset created explicitly. " & _ "Data in the cache has been modified since " & _ "its last viewing." End If End If End With End Sub Private Sub LoadDataCache() Dim dsCache As DataSet ' Loads the local data set with the data from the ' "Order Details Extended" view dsCache = LoadDataSet("""Order Details Extended""") ' caches local data set in "MyData" Cache.Insert("MyData", dsCache) End Sub Private Function CachedRowCountEqualsDatabaseRowCount( _ ByVal dsCached As DataSet) As Boolean ' Retrieves the data set from the Order_Details_Record_Count_vw _ ' view Dim dsRecordCount As DataSet = _ LoadDataSet("Order_Details_Record_Count_vw") ' Gets the RecordCount value from the data set Dim iRecordCountOfDatabaseTable As Int16 = _ dsRecordCount.Tables(0).Rows(0).Item("RecordCount") ' Gets the record count of the cached data set Dim iRecordCountOfCachedTable As Int16 = _ dsCached.Tables(0).Rows.Count ' If the cached table row count equals that of the database ' row count, then now records were added or removed. In this ' case, return True. Otherwise, return False If iRecordCountOfCachedTable = iRecordCountOfDatabaseTable Then Return True Else Return False

Page 37: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

End If End Function Private Function LoadDataSet(ByVal sViewName As String) As DataSet ' Builds the SQL statement that we'll be running to load ' the data set Dim sSQLStatement As String = "Select * FROM " & sViewName Dim daGeneric As SqlDataAdapter Dim dsGeneric As DataSet ' Instantiates a new data adapter based on the SQL ' statement that is passed in. daGeneric = New SqlClient.SqlDataAdapter(sSQLStatement, _ New SqlClient.SqlConnection( _ Session("ConnectionString"))) Try With daGeneric .SelectCommand.CommandType = CommandType.Text dsGeneric = New DataSet ' fills the data set with the returned records .Fill(dsGeneric) End With Return dsGeneric Catch ex As Exception ErrorMsg.Text = "There was a problem loading the data set." End Try End Function Listing 11 Code to cache data and retrieve data from the cache In the page load event, we’ll make a call to the LoadData method. The first thing that we do when we call LoadData() is we load the ReportDocument object with the report, and the viewer with the ReportDocument. Next, we check to see if the Cache("MyData")is defined. If it isn’t defined, then that means that there wasn’t any data in the cache. We’ll then populate the cache by calling the LoadDataCache method, and then set the report’s SetDataSource method to the cache. If however, the cache was defined when we load the report, then we must check to see if there have been changes made to our data. In this simple caching sample, we verify that the number of rows in the cache equals the number of rows in the corresponding database table. We call up a view to get the record count of our queried (Order Details) table. See listing 12 for the view that returns the row count in the database. In the case where the number of rows in the cached data set, matches the record count of the database, then we assume that no changes were made to the database, and so we populate the report with the cached data. In the case where these record counts don’t match, then there must have been a change to the data. In this case, we re-query the data and update the cache before loading the report. CREATE VIEW Order_Details_Record_Count_vw

Page 38: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

AS Select Count (*) AS RecordCount From "Order Details" Listing 12 View which returns the record count of the Order Details table Now, obviously this is a rudimentary caching sample. For instance, this strategy doesn’t take into account if an edit was made to the existing record. A strategy like this really is only useful if we’re checking a table that only allows inserts and deletes. However, the important thing to understand is that customizing the data caching of our reports is possible. We could have also created a sample where there were a trigger on the table that fires when an update, an insert, or a delete occurs; and in that trigger, we update a flag in another table that checks for table modifications. Then when we go to load our report, we check to see if changes have been made to the table by checking the flag. If changes were made, then we could re-query the table, and reset the flag to show that no modifications were made. If no changes were made, then we would load our report from the cache. Whatever the implementation that you choose to custom you data caching, the important thing is that you as the developer have full control over its design. Exporting To the Stream Object We looked at exporting our report content to disk in our Windows exporting samples. In our Web sample, let’s look at how we would export our report content to a stream, and export it as a PDF to the Response object of the browser. One of the limitations of Web reporting is that the Crystal Reports Web Viewer control doesn’t have its own print capabilities. Instead, it just ties into the browser print model. As anyone has tried to print a document from the browser knows, this option offers little control over pagination. This is where exporting our report to a PDF format comes in handy. PDFs have their own print model (via the Adobe/Acrobat Reader) and they fully support pagination. As we continue to build on our frmCache.aspx Web form, we’ll add the code illustrated in Listing 13 to stream our report content into a PDF format. Private Sub btnExportToStream_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnExportToStream.Click ' creates the stream object Dim stream As System.IO.Stream ' Reloads the report data LoadData() ' Exports the report contents to a stream object stream = rdDataCache.ExportToStream( _ ExportFormatType.PortableDocFormat) ' set the http headers Response.ClearContent() Response.ClearHeaders() ' set the MIME type to PDF Response.ContentType = "application/pdf"

Page 39: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

' read the stream as binary (a byte array) and ' write the byte array to the browser Dim b(stream.Length) As Byte stream.Read(b, 0, stream.Length) Response.BinaryWrite(b) Response.End() End Sub Listing 13 Code that streams our report content into a PDF format In our example, we’re calling the report document’s ExportToStream method and passing in the ExportFormatType.PortableDocFormat. From there, we’re reading the stream into a byte array, and then writing that byte array to the browser. The result is that we’ll see the Adobe Reader, displaying the PDF version of our report, embedded in the browser. All of the printing functionality of our Adobe Reader can now be leveraged. Deployment When deploying a .NET application that contains report files (.rpt), the report files may or may not have to be distributed along with the application. .NET provides two different methods to accomplish distribute the reports. You can choose to either compile the report files into the application, or distribute the report files separately from the application.

Embedded Report Files

When reports are added to a .NET Windows or Web application, they are added by default as an Embedded Resource for the application. This means that the report files will be compiled into the assembly, and will not be loaded from a separate report file.

The advantage of embedded report files is that you don't have to distribute external report files, and end users cannot modify them. This provides a much more secure design since the reports can’t be modified by the end user. The disadvantage is that if a report needs to be modified, the entire application needs to be recompiled and redeployed.

Non-Embedded Report Files

Non-embedded report files are report files that are not compiled into an application's assembly and are distributed separately from the application. To prevent reports from being compiled into the assembly, select the report in the Solution Explorer and right-click on the report name. Choose the Properties option. From the Properties window, change the Build Action property from Embedded Resource to None. Reports not compiled into the assembly are loaded using the ReportDocument object’s Load method. Also, the report files must be manually added to the setup project to be distributed with the application.

The advantage of keeping reports outside of the assembly is that reports can be modified and redeployed without having to recompile and redeploy the entire application. The disadvantage is that strongly-typed report objects cannot be used in the application and reports must be loaded explicitly from .rpt files.

Page 40: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

Merge Modules In order for your reports to run properly, you need to distribute Crystal Reports merge modules with your application. Crystal Reports for Visual Studio .NET 2003 uses merge modules for deployment. Merge modules are provided for developers who want to include Crystal Reports runtime files along with their application. A merge module is a simplified installer file. Merge modules install components used by the application. Crystal Reports merge modules need to be added to the setup project to successfully deploy an application that provides reporting functionality. Different versions of Crystal Reports require different merge module files. Below is a detailed of the various Crystal Reports for Visual Studio .NET merge modules and the functionality covered by each. All but the VC_User*.msm merge modules are required to be distributed with your application. File Name Description Crystal_Managed2003.msm Used to install the Crystal Reports for VS.NET

managed components Crystal_Database_Access2003.msm Used to install database drivers used by the

reports. Also installs export destinations and format drivers.

Crystal_Database_Access2003_enu.msm Installs language specific components. Also installs PDF components.

Crystal_RegWiz2003.msm Configures registration and licensing. A license key must be provided for this module when building a setup project. You will be prompted when you add this file to enter a license key. This key will be emailed to you when you register Crystal Reports.

VC_User_CRT71_RTL_X86_---.msm and VC_User_STL71_RTL_X86_---.msm

Optional. Required only when reports use ADO.NET DataSet objects.

By default, these files are located in the C:\Program Files\Common Files\Merge Modules\ folder. To set up a merge module, right click on the solution in the Solution Explorer window and choose Add New Project… from the menu. For a Web application, choose the Setup and Deployment Projects folder in the left pane, then select the Web Setup Project icon in the right pane. For a Windows application, choose the Setup and Deployment Projects folder in the left pane, then select the Setup Project icon in the right pane. Right-click on the Setup Project file in the Solution Explorer window and choose Add Project Output… from the menu. For Web applications, select the Primary output and Content Files items from the list and click OK. For Windows applications, select the Primary output option from the list and click OK. Project dependencies will be detected with the OK button is clicked. Add the merge modules listed above to the setup project by right-clicking the setup project in the Solutions Explorer window and selecting Add Merge Module… from the menu. Select the appropriate files from the dialog. Right click on the Crystal_RegWiz2003.msm file a view its properties. Expand the MergeModuleProperties property to enter the 19 digit license key value. The license key can be obtained from Help About in VS.NET 2003. To deploy the application,

Page 41: Beyond the Basics: Advanced use of Crystal Reports for ... · This article is based on Crystal Reports as provided with Visual Studio .NET 2003 This article uses the following technologies:

right click on the setup project in the Solution Explorer window and choose the Build option. Once the project is built, it is ready to deploy to the appropriate platform.

Conclusion Moving past the designer view of Crystal Reports can open up a new and exciting world of reporting options. Exploring the object model of the various CrystalDecisions namespaces reveals ways to programmatically manipulate your reports’ appearance, their underlying data structure, and a number of content and formatting options. The design and implementation of more flexible multipurpose reports can save development teams countless hours of report design effort to meet individual specifications. Besides the advantages that this poses to developers, end users will appreciate the power of being able to serve up their own data in a format that’s tailored to their specific needs. The samples that we’ve discussed here have merely scratched the surface of what’s possible with these advanced techniques. Further exploration of the object model will be worthwhile and rewarding to developers with heavy reporting requirements to implement. Additional Resources

• Add Professional Quality Reports to Your Application with Visual Studio .NET – May 2002 MSDN Magazine - Andrew Brust http://msdn.microsoft.com/msdnmag/issues/02/05/Crystal/

• Crystal Reports For VS.NET 2003 Startup Guide – Business Objects DevZone Web site -- Paul Delcogliano http://www.businessobjects.com/products/dev_zone/gate/default.asp?file=cr_startguide&ref=devzone_netzone_resources