Special Edition Using Visual Basic.NET

function OpenWin(url, w, h) { if(!w) w = 400; if(!h) h = 300; window.open(url, "_new", "width=" + w + ",height=" + h + ",menubar=no,toobar=no,scrollbars=yes", true); } function Print() { window.focus(); if(window.print) { window.print(); window.setTimeout('window.close();',5000); } }
Team-Fly    

Special Edition Using Microsoft® Visual Basic® .NET

By Brian Siler, Jeff Spotts

Table of Contents
Chapter 23.  Creating and Using Reports

The .NET framework includes several classes you can use to interact with your printer. You can print text and graphics, as well as control printer settings such as margins and resolution. In addition, several classes create dialog boxes that make it easy for the end user to select printers or view a report onscreen before sending it to the printer. Printing in Windows Forms is graphics-based. Even if you want to print only lines of text, you still lay out your printed pages using graphics methods. These methods are the same methods that you use to draw on a Windows form and are located in the System.Drawing.Graphics namespace.

For more on graphics methods, p. 357.

The classes that provide the additional functionality necessary to send graphics to the printer are located in the System.Drawing.Printing namespace. In this section, we will introduce some of the many classes in the .NET framework used to facilitate the printing process. In doing so, we will create a sales report for a cat food company.

Planning a Report

Relative to computer operations, a report is a very high-level concept, such as a report listing sales figures by employee. To implement a report with Windows Forms, you must translate the reporting requirements into lower-level method calls that tell the printer what it needs to do. Often, it is helpful to separate the report requirements into two basic areas:

  • Report data Most reports are based on database data. Before you write a line of Visual Basic code, you should know what fields you want on the report, as well as have the SQL query created. In the case of our sample report, we'll need to display the salesperson's name, number of orders, and total amount of the orders.

  • Report layout Designing a report is similar to designing a form. However, when you create a report entirely with code, you don't have the advantage of immediate visual feedback. For the sample report, we will have a simple three-column layout with the data fields previously mentioned. In addition, our report will have a header on each page containing the report title and company logo.

To begin following along with the example, perform the following steps:

  1. Start a new Windows application project in Visual Studio .NET.

  2. Add a Button control to the form. Set its Name property to btnPrint and its Text property to Print Report.

  3. Open the code window for the form and add the following Imports statements to the top of the form class:

    Imports System.Drawing.Printing Imports System.Data.SqlClient

  4. For the sample report, we will use data from the Northwind database, which is included with the .NET Framework SDK. To install this sample database, click the Start button, and then find the Microsoft .NET Framework SDK program group. Select Samples and QuickStart Tutorials.

  5. From the Samples and QuickStart tutorials page, follow the steps to install and configure the samples. This process will install an instance of the Microsoft Database Engine containing the Northwind database.

In the remainder of this section, we will describe the code necessary to connect to the database and produce the sample sales report.

Creating a PrintDocument Object

If you have used previous editions of Visual Basic, such as version 6.0, you might recall using the Printer object. Although Windows Forms is the .NET equivalent to this type of printing, there is a big change in that printing is now event driven. Instead of continuously directing the printer operation from the start to finish of your printed document, you write code for several event procedures and associate them with a print job. The .NET Framework calls these event procedures when needed. Although this is a different way of thinking in relation to controlling the printer, it gives you the freedom to concentrate more on formatting your report, as well as provides some great new features such as print preview. In the sample project, you will create the following custom procedures in the form class:

  • PrintSalesReport Initiates the printing process

  • PrintSalesPage Event procedure that handles printing a single page

  • PrepareReportData Establishes the database connection and retrieves data

  • ReportProcessed Event procedure that is called when the report is finished

The PrintDocument class represents a print job to your application. To create a new print job, you simply create an instance of this class and add the appropriate event handlers. To get started on the sample project, add the following function to the form class:

Private Sub PrintSalesReport() Dim SalesReport As PrintDocument SalesReport = New PrintDocument() AddHandler SalesReport.PrintPage, AddressOf PrintSalesPage AddHandler SalesReport.BeginPrint, AddressOf PrepareReportData AddHandler SalesReport.EndPrint, AddressOf ReportProcessed SalesReport.Print() End Sub

The PrintSalesReport subroutine demonstrates the typical technique for using the PrintDocument class. First, the SalesReport object is instantiated. Next, the PrintPage event is connected to an event handler. Finally, the Print method is called, which actually initiates the printing process.

Note

Although entirely optional, you can also set the DocumentName property to a name that will be displayed in the Windows Print Manager and other dialog boxes.

After you have entered the PrintSalesReport function, add the following line of code to the Click event for btnPrint:

Call PrintSalesReport()

Now that you have created the code to start the print job, you need to add code to process the job's events.

Understanding the PrintPage Event

When you create a print job in Windows Forms, you have to write code that generates one page at a time, in response to the firing of the PrintPage event. To handle this event in our sample project, enter the code in Listing 23.1, which handles one page of the sales report.

Listing 23.1 PRINTINGEXAMPLE.ZIP Coding the PrintPage Event

Sub PrintSalesPage(ByVal sender As Object, ByVal e As PrintPageEventArgs) Const NameX As Integer = 100 Const OrderX As Integer = 300 Const SalesX As Integer = 500 Dim CompleteName As String Dim RecordsPerPage As Integer = 20 Dim CurrentRecord As Integer = 0 Dim CurrentY As Integer = 300 Dim ReportFont As New Font("Arial", 12, FontStyle.Regular) Dim ReportFontHeight As Integer = ReportFont.GetHeight(e.Graphics) While CurrentRecord < RecordsPerPage 'Place information from data reader current record on the page CompleteName = rdrSalesInfo.GetString(1).Trim & _ ", " & rdrSalesInfo.GetString(0).Trim e.Graphics.DrawString(CompleteName, ReportFont, _ Brushes.Black, NameX, CurrentY) e.Graphics.DrawString(rdrSalesInfo.GetInt32(2).ToString, _ ReportFont, Brushes.Black, OrderX, CurrentY) e.Graphics.DrawString(Format(rdrSalesInfo.GetValue(3), _ "$##.00"), ReportFont, Brushes.Black, SalesX, CurrentY) CurrentY = CurrentY + ReportFontHeight 'Move to the next record If Not rdrSalesInfo.Read() Then Exit While End If CurrentRecord += 1 End While If CurrentRecord < RecordsPerPage Then e.HasMorePages = False Else e.HasMorePages = True End If End Sub

The subroutine in Listing 23.1 is an event-handling routine for the PrintPage event. The PrintDocument object will keep firing the event until the HasMorePages property of the second event parameter, e, is set to False. Not only does this event parameter control when printing is complete, but it also provides the Graphics object that your code uses to render the page. Notice that each time you draw a line of text with methods of the e.Graphics object, you have to specify the location of the text using the x,y coordinate. Therefore, when placing text fields on the page, the sample code uses a counter variable called CurrentY to determine how far down on the page to start drawing text. (The GetHeight method of the Font class returns the height of a line of text in that font.)

Note that when programming the PagePrint event, it is up to the programmer to know what data needs to be printed. Our sample code uses a SQLDataReader, which has a method to advance to the next record. However, if you were using an array, you might use a variable to indicate the current array index. If you only need to print a single page report, you can just set the HasMorePages property to False at the end of your event handler.

Using Other PrintDocument Events

At a minimum, you need to write code for the PrintPageevent to provide the contents of each page to the PrintDocument object. However, two additional events are available that make it easy to perform initialization and cleanup tasks when printing a report. The BeginPrint and EndPrint events are raised before and after printing document pages, respectively. Because the code in Listing 23.1 assumes that you have an open SQLDataReader object, the BeginPrint event is a great place to connect to the database and initialize this object. Similarly, the EndPrint event procedure can be used to terminate the database connection. To continue with the sample project, enter the code in Listing 23.2, which demonstrates how these events can be used to set up a SQLDataReader object and inform the user when the print job is complete.

Listing 23.2 PRINTINGEXAMPLE.ZIP Using the BeginPrint and EndPrint Events

Dim rdrSalesInfo As SqlDataReader Dim cn As SqlConnection Private Sub PrepareReportData(ByVal sender As Object, ByVal e As PrintEventArgs) Dim sSQL As String Dim sConnectString As String Dim cmd As SqlCommand 'Open SQLDataReader and position to first record sConnectString = "server=localhost\NetSDK;uid=sa;pwd=;database=NorthWind" cn = New SqlConnection(sConnectString) sSQL = "SELECT E.FirstName, E.LastName, Count(*) As TotalOrders, Sum(O.SubTotal) AS TotalSales sSQL &= " FROM Employees E INNER JOIN " sSQL &= " (Orders INNER JOIN [Order Subtotals] O ON Orders.OrderID = O.OrderID)" sSQL &= " ON E.EmployeeID = Orders.EmployeeID" sSQL &= " GROUP by E.LastName, E.FirstName" cmd = New SqlCommand(sSQL, cn) cn.Open() rdrSalesInfo = cmd.ExecuteReader() rdrSalesInfo.Read() End Sub Private Sub ReportProcessed(ByVal sender As Object, ByVal e As PrintEventArgs) cn.Close() MessageBox.Show("You report has been sent to the printer!") End Sub

The PrepareReportData function in Listing 23.2 creates a SQLDataReader object and positions it to the first record. Note that the rdrSalesInfo object is declared at the class level because it needs to be accessible from the PrintPage event handler, shown earlier in Listing 23.1.

To learn more about using a SQL data reader, p. 599.

The ReportProcessed function closes the SQL connection, as well as displays a message indicating report processing is complete. In order for these two functions to be called at the appropriate times, they need to be connected to events of a PrintDocument object with AddHandler statements. As you may recall, the PrintSalesReport function defined previously contains these statements.

The second parameter of the BeginPrint and EndPrint event delegates is of type PrintEventArgs, which is different from the PrintPage event's PrintPageEventArgs parameter. One useful property of this parameter type is the CancelPrint property, which can be used to cancel printing the report for example, if no data exists in the database. In addition, you also should add exception handling logic so that your program terminates the print job in case of an error.

To learn more about handling exceptions, p. 113.

Testing the Sample Report

If you have been following along with the example and have a printer attached to your computer, you can go ahead and test the sample program by starting the project and clicking the Print button on the form. If all goes well, you should see the message displayed informing you that the report has been sent to the printer. Note that, because Windows buffers the output sent to your printer, you probably will see the message indicating the print job is complete well before your printer has finished processing it. When the print job has been submitted to Windows, you can close your Visual Basic application and printing will continue.

Printing Relative to Margins

If you followed the example and printed a sales report, you know that creating your report layout is as simple as specifying the desired x,y coordinate of each report element. The code in Listing 23.1 used the graphics method DrawString to render text on the graphics object at specific x,y coordinates. Although the y coordinate was determined dynamically using a counter, we hard-coded the x coordinate for each column of text using constant values:

Const NameX As Integer = 100 Const OrderX As Integer = 300 Const SalesX As Integer = 500

The values listed previously are in the default graphics unit of Display, which is 1/75 of an inch. Hard-coding positions like this may work for specific reports and printers, but it is not a very flexible approach.

Note

To learn about the different types of measurement units available for use with graphics methods, search for "GraphicsUnit" in the help files.

To aid in locating objects relative to margins and page boundaries, the second event argument to the PrintPage event includes some useful properties:

  • MarginBounds Used to determine the margins of the current page.

  • PageBounds Used to determine the boundaries of the current page; useful for centering objects or creating footers. As noted in the help files, many printers cannot print all the way to the edge of the page.

Both of these properties return a Rectangle object, which itself contains properties you can use to determine its size and location. The code in Listing 23.3 shows how we can replace our hard-coded column positions from Listing 23.1 with columns that respect the margins and are based on a percentage of the page width.

Listing 23.3 PRINTINGEXAMPLE.ZIP Determining Column Widths

Private Function GetColX(ByVal ColNum As Integer, ByVal Boundary As Rectangle) As Single Dim colWidthPct() As Integer = {70, 20, 10} If ColNum = 0 Then 'The first column is placed at the page margin Return Boundary.X Else 'X is at the end of the previous column Dim CurrentX As Single Dim i As Integer For i = 0 To ColNum - 1 CurrentX = CurrentX + (colWidthPct(i) / 100) * Boundary.Width Next Return CurrentX End If End Function

The column widths are defined by percentages in the colWidthPct array. Listing 23.3 uses the percentages to determine how many display units each column should take up. To call the GetColX function, you simply pass the zero-based column index and MarginBounds properties as arguments:

NameX = GetColX(0, e.MarginBounds)

Although this function helps you determine the x coordinate, it does not return the width of the column or prevent text from escaping column boundaries. However, compared to the previous method of locating objects on the report (hard-coded values), it provides an additional level of flexibility for the programmer. If you are interested in learning about other graphics methods that can help you with report layout code, see the help topic "System.Drawing Namespace."

Enhancing the Printing Experience

Although we have shown the techniques necessary to send information to the printer, most users expect some control over the print job, or at least the opportunity to cancel the print request. In other words, printing immediately in response to a button click is not a very professional way to handle printing. In order to meet user expectations, you may also want to add some of the following features, which are common printing enhancements in Windows programs:

  • Print Preview Lets the user view the pages of the document on the screen before sending them to the printer

  • Printer Selection Allows the user to select from the various printers installed on his system

  • Page Setup Provides the ability to change margins or other print quality settings

With each of the preceding features, you can determine how much control you want to give the end user over the printed output by using the included dialog box classes or setting properties manually with code. Some applications, such as Microsoft Word, allow the user to control detailed settings through the Print menu, while still including a toolbar button for quick printing. In the following sections, we'll explore how to implement these features in our sample report.

Adding Print Preview

The .NET framework provides two classes that enable you to add print preview capability to your application:

  • PrintPreviewDialog Provides a preview of your report on a separate form that contains a toolbar with buttons for zooming, printing, and selecting pages

  • PrintPreviewControl Provides a preview of your report that can be placed on an existing form to customize the print preview process

Each of these classes is also available as a control in your toolbox, although in the case of the PrintPreviewDialog class it is just about as easy to create the class entirely with code. Thinking back to our sales report from Listing 23.1, Listing 23.4 shows how to create a print preview dialog box for the report.

Listing 23.4 PRINTINGEXAMPLE.ZIP Implementing Basic Print Preview

Dim SalesReport As PrintDocument Dim dlgPreview As PrintPreviewDialog SalesReport = New PrintDocument() dlgPreview = New PrintPreviewDialog() AddHandler SalesReport.PrintPage, AddressOf PrintSalesPage dlgPreview.Document = SalesReport dlgPreview.ShowDialog()

To try the code in Listing 23.4, perform the following steps:

  1. Add a Button control to your form. Set its Name property to btnPreview and its Text property to Print Preview.

  2. Enter the code from Listing 23.4 to the Click event procedure for the button.

  3. Run the sample project and click the button to preview the report. The preview window should look similar to the one pictured in Figure 23.1.

    Figure 23.1. It takes only a few extra lines of code to add a print preview form to your printed reports.

Notice that nowhere in Listing 23.4 do we invoke the Print method of the PrintDocument class. Instead, the dlgPreview object handles sending the report to the printer.

The toolbar on the preview dialog box includes many useful features, such as zoom and the ability to switch pages. It appears as an independent form in your Windows application. This is one way to implement print preview capability. However, some applications present the preview screens embedded within an existing window instead of opening a new one. If you want to embed print preview capability in one of your existing forms, you need to use the PrintPreviewControl class instead of the PrintPreviewDialog class. Figure 23.2 shows an example of a form containing a PrintPreviewControl control.

Figure 23.2. A Print Preview control provides the ability to include the print preview window on an existing form.

As with the PrintDialogControl, you follow the same basic steps to use the PrintPreviewControl:

  1. Assign a print document object to the PrintPreviewControl.

  2. Call the Show method to initiate the print preview.

Because the PrintPreviewControl class only provides the actual document view (and not the toolbar), you will have to write your own code to control page display, zooming, and other operations. In addition, you may use the following properties of the PrintPreviewControl to customize its appearance:

  • Columns and Rows These properties determine how many pages will be displayed in the preview window at the same time.

  • Startpage Sets the page being displayed in the preview window, or, if multiple pages are displayed, the page in the upper-left corner.

  • Zoom and AutoZoom Controls the size of the report in the preview window.

Although the PrintPreviewControl class does not have a print method built in, you can use the Print method of the PrintDocument object to send the report to the printer:

PrintPreviewControl1.Document.Print()

The previous line of code demonstrates that you can use the Document property of a PrintPreviewControl control to print the associated document. You could have just as easily used a reference to the PrintDocumentObject itself.

Controlling Page Settings

As we mentioned earlier, you can write printing code that is aware of margins and page boundaries. Sometimes you might want to give control over these boundaries to the user of your program. The .NET Framework provides classes that allow you to set margins programmatically or through a user dialog box. The code in Listing 23.5 shows how to do both; it first sets default page orientation and margins, but then it displays a Page Setup dialog box, which the user can use to change the defaults. The Page Setup dialog box is pictured in Figure 23.3.

Figure 23.3. The PageSetup Dialog class can be used to display a standard Windows Page Setup dialog box.

Listing 23.5 PRINTINGEXAMPLE.ZIP Setting Page Margins

Dim MySettings As PageSettings Dim UserSetup As PageSetupDialog 'Set up your default settings MySettings = New PageSettings() MySettings.Landscape = True MySettings.Margins = New Margins(150, 150, 150, 150) 'Display a dialog so the user can set their own UserSetup = New PageSetupDialog() UserSetup.PageSettings = MySettings UserSetup.ShowDialog() 'Now associate the settings with the document SalesReport.DefaultPageSettings = MySettings

Notice that you must set the PageSettings property of the PageSetupDialog object to retrieve the settings from it. Listing 23.5 also introduces the Margins class. Margins are specified in hundredths of an inch. The sample code sets the page margins to one and one-half inches on all sides.

Note

As with other dialog box forms, you can check the result of the ShowDialog method to determine if the user clicked the Cancel button.

To learn more about the ShowDialog method, p.335.

Note

If you set the DefaultPageSettings property, it will control page settings for the entire document. However, you can programmatically override these settings on a per-page basis by handling the QueryPageSettings event of the PrintDocument object.

The Page Setup dialog box is divided into several sections: margins, orientation, paper, and so on. You can limit the user from changing these by setting properties of a PageSetupDialog object:

UserSetup.AllowOrientation = False

The preceding line of code disables the orientation section of the Page Setup dialog box, although you can still set it in program code.

Determining Printer Settings

Not all printers are created equal. There is a wide variation in features and print qualities. Not to mention you may have several printers attached to your computer. To make your Windows forms printing more successful, Microsoft has provided the PrinterSettings class. This class allows you to list the printers on your system and determine their capabilities, as well as control printer settings. The code in Listing 23.6 enumerates all the printers on the system, displaying a message regarding their color printing capabilities.

Listing 23.6 PRINTINGEXAMPLE.ZIP Querying Printer Information

Dim MySettings As New PrinterSettings() Dim PrinterNameList As PrinterSettings.StringCollection Dim CurrentPrinterName As String Dim i As Integer Dim s As String PrinterNameList = PrinterSettings.InstalledPrinters For Each CurrentPrinterName In PrinterNameList MySettings.PrinterName = CurrentPrinterName If MySettings.SupportsColor Then s = CurrentPrinterName & " is a color printer, " s &= " supporting the following resolutions:" & vbCrLf For i = 0 To MySettings.PrinterResolutions.Count - 1 s &= MySettings.PrinterResolutions(i).ToString & vbCrLf Next Else s = CurrentPrinterName & " is not a color printer." End If MessageBox.Show(s) Next

The PrinterSettings class has one static property, InstalledPrinters, which can be used to retrieve a list of printers by name. To determine the features and settings of an individual printer, you have to first assign the printer to an instance of the PrinterSettings class by setting the PrinterName property.

Note

In production applications, remember to add exception-handling code to handle printing errors.

To learn more about exception handling, p. 113

Displaying a Print Dialog Box

As an alternative to setting individual properties of a PrinterSettings object, you can allow the user to control printer settings interactively by displaying a Print dialog box. A Print dialog box, shown in Figure 23.4, allows the user to select a printer as well as control the number of copies and other printer-specific settings.

Figure 23.4. Printer selection and page setup can be accomplished through the Print dialog box.

To create a Print dialog box, you first instantiate the PrintDialog class, then connect it to a report via its Document property, and finally invoke the ShowDialog method. Listing 23.7 shows how to display a Print dialog box.

Listing 23.7 PRINTINGEXAMPLE.ZIP Using the PrintDialog Object

Dim SalesReport As PrintDocument Dim PrinterSetupScreen As PrintDialog Dim ButtonPressed As DialogResult 'Set up the report SalesReport = New PrintDocument() AddHandler SalesReport.PrintPage, AddressOf PrintSalesPage AddHandler SalesReport.BeginPrint, AddressOf PrepareReportData AddHandler SalesReport.EndPrint, AddressOf ReportProcessed 'Display printer dialog PrinterSetupScreen = New PrintDialog() PrinterSetupScreen.Document = SalesReport ButtonPressed = PrinterSetupScreen.ShowDialog() 'Print, if user clicked OK If ButtonPressed = DialogResult.OK Then SalesReport.Print() End If

As with the PageSettingsDialog class, there are several properties of the PrintDialog class you can use to customize the dialog box. For example, you can optionally display options that allow the user to select a range of pages for printing. However, because you have to provide the maximum and minimum pages, you have to know in advance how many pages are in the final document.


    Team-Fly    
    Top
     

    Категории