rinting is an integral functionality found in almost every decent Windows application. For example, you might be working on a POS (Point of Sale) application and need to print receipts, or you may want to print out employee records from a database. In either case, you need to be able to send the output directly to the printer. What’s more, you must have the capability to format the printout the way you want it to appear on paper.
Fortunately, the System.Drawing.Printing namespace in .NET contains all the necessary classes for printing-related services. Specifically, using the PrintDocument class, you can now print from your Windows-based application with ease.
In this article, I will show you how to print from your Windows-based .NET applications. To do so, I’ll build a sample application that prints employee name cards using data from the SQL Server Northwind sample database. If you do not already have access to the Northwind database in your environment, you can install it from the freely downloadable SQL Server 2005 Express edition or you can download the Northwind and pubs sample databases for SQL Server 2000.
|Author’s Note: The printing logic described here should work in VS.NET 2003; but the drag-and-drop data-binding shown requires VS 2005.|
Creating the Application
Using Visual Studio 2005, create a new Windows application and name the project C:Printing. You will use the data-binding feature in Windows Forms 2.0 to display records from the Employees table in Northwind.
First, add a data source to the project by selecting Data?>Add New Data Source?. The Data Source Configuration Wizard will appear. Select Database, and click Next. Click the “New Connection?” button to create a connection to the SQL Server 2005 Express database server (henceforth referred to as SQL Express).
In the Add Connection window, enter “.SQLEXPRESS” as the server name (this refers to the SQL Express database server installed on the current computer). Then, select the Northwind database (see Figure 1). Click OK.
Back in the Data Source Configuration Wizard, click Next. In the “Choose Your Database Objects” window, expand the Employees table and check the following fields (see Figure 2):
Click Finish. In the Data Sources window (Data?>Show Data Sources), you will now see the Employees table listed under the NorthwindDataset item. Click the Employees drop-down ListBox and select Details (see Figure 3). This will change the binding of the Employees table from the default DataGridView to individual controls representing each individual field. Also, click on the Photo drop-down ListBox and select PictureBox. This will bind a PictureBox control to the Photo field.
You are now ready to display the selected records on the Windows form. Drag the Employees table from the Data Sources window and drop it onto the default Windows Form1. The default Form1 should now look like Figure 4.
|Author’s Note: Remember to set theSizeMode property of the PictureBox control (displayed next to the Photo label as a rectangle) to AutoSize. This will allow the photos of the employees to be displayed in their original size.|
|Figure 5. Scanning Faces: You can add bar-coding capability to your Windows application.|
To make the application more interesting, I’ll add a barcode to the form that contains information about the employee (in this case the employee ID and the employee name). To create it, I used the Barcode Professional control from Neodynamic. You can download a free trial of this tool or purchase a Developer license for $200.
After installing this control, you can add it to your Toolbox and then drag and drop it onto the form, as shown in Figure 5. Using the barcode control’s SmartTag, set the Barcode Symbology property to Code128. Don’t worry about the symbology, it’s sufficient to know that different barcode applications use different symbologies. For example, books use ISBN (another barcoding symbology), etc.
|Author’s Note: You may need to shift the other controls around to make way for the barcode control.|
Coding the Form
You are now ready to code the application. Double-click on Form1 to switch to the code-behind. In the Form1_Load event, code the following:
Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Me.EmployeesTableAdapter.Fill( Me.NorthwindDataSet.Employees) updateBarcode() End Sub
The updateBarcode() subroutine in the preceding code updates the encoding on the barcode based on the current record displayed as shown below:
Private Sub updateBarcode() BarcodeProfessional1.Code = _ EmployeeIDTextBox.Text & "-" & _ LastNameTextBox.Text & " " & _ FirstNameTextBox.Text End Sub
|Figure 6. Adding a Print Button: Add a new button control to the form so users can access the print functionality.|
When the current record changes, you also need to update the barcode. You do this through the CurrentChanged event of the EmployeesBindingSource control (which Visual Studio automatically adds to the form when you drag and drop the Employees table from the Data Sources window) and add a call to the updateBarcode() subroutine:
Private Sub EmployeesBindingSource_CurrentChanged( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles EmployeesBindingSource.CurrentChanged updateBarcode() End Sub
Adding Print Functionality
The next step is to add printing functionality to the form. Add a Button control to the bottom of the form and name it btnPrint (see Figure 6).
The Print button formats and sends the on-screen record to the printer. It will print a name card containing the current employee’s details. Figure 7 shows what the name card will look like when printed. The positions of the various elements in the name card are stored in predefined constants.
|Figure 7. Name Card: The figure shows the various positions of the information on the name card.|
Switching to the code-behind of Form1, import the following namespace:
This namespace contains the classes for printing in Windows Forms. Next, define the following constants and variables:
Const COMPANY_NAME As String = "The XYZ Corporation" Const MARGIN_X As Integer = 10 Const MARGIN_Y As Integer = 10 Const CARD_WIDTH As Integer = 350 Const CARD_HEIGHT As Integer = 200 Const COMPANY_NAME_Y As Integer = 110 Const EMPLOYEE_NAME_Y As Integer = 150 Const EMPLOYEE_TITLE_Y As Integer = 165 Const EMPLOYEE_DOB_Y As Integer = 180 Private f_companyname As Font Private f_employeename As Font Private f_title As Font Private f_DOB As Font
When users click on the Print button, you first instantiate an instance of the PrintDocument class. The PrintDocument class defines the various methods that allow you to send output to the printer. There are three main events that you need to service in order to use the PrintDocument class to send output to the printer. They are:
- BeginPrint?Occurs when the Print method is called and before the first page of the document prints. Typically, you make use of the BeginPrint event to initialize fonts, file streams, and manipulate other resources used during the printing process.
- PrintPage?Occurs when the output to print for the current page is needed. This is the main event where you code the logic required for sending the output to the printer.
- EndPrint?Occurs after the last page of the document has printed. Typically, you use the EndPrint event to release fonts, file streams, and other resources.
To start the printing process, use the Print() method of the PrintDocument class.
Code the Print button’s Click event as follows:
Private Sub btnPrint_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnPrint.Click Dim printDoc As New PrintDocument() AddHandler printDoc.BeginPrint, _ New PrintEventHandler(AddressOf Me._beginPrint) AddHandler printDoc.PrintPage, _ New PrintPageEventHandler( _ AddressOf Me._printPage) AddHandler printDoc.EndPrint, _ New PrintEventHandler(AddressOf Me._endPrint) printDoc.Print() End Sub
In the BeginPrint event handler, initialize the fonts used while printing:
Private Sub _beginPrint( _ ByVal sender As Object,ByVal e As PrintEventArgs) '---initialize the fonts--- f_employeename = New Font("Arial", 10, _ FontStyle.Bold) f_title = New Font("Arial", 8) f_DOB = New Font("Arial", 8, FontStyle.Italic) End Sub
When printing ends, de-reference all the font variables used in the EndPrint event handler:
Private Sub _endPrint( _ ByVal sender As Object, _ ByVal e As PrintEventArgs) '---de-reference the fonts--- f_employeename = Nothing f_title = Nothing f_DOB = Nothing End Sub
You do the bulk of the work to send output to the printer in the PrintPage event handler by using the Graphics object in the PrintPageEventArgs class to specify the output you desire. For example, to draw a rectangle you would use the e.Graphics.DrawRectangle() method (where e is an instance of the PrintPageEventArgs class). To print a string, you use the e.Graphics.DrawString() method. You can specify the font to be used for each string by using the Font class.
Code the PrintPage event handler as shown in Listing 1.
That’s it! To test the application, press F5. You can navigate to a particular employee and click the Print button. Your printer should print the name card of the selected employee.
|Author’s Note: You need to ensure that you have at least one printer driver installed on your computer. The printout will be sent to the printer configured as the default printer.|
Previewing the Printout
So far, you’ve seen how to print out the name card of the current employee. But what about preview functionality? During the development stage it would be very useful to see what the printout would look like before you send it to the printer (helping to save a few trees). To include print preview functionality, add a second button to the bottom of the form and name it btnPreview (see Figure 8).
|Figure 8. Coming Attractions: Add a new button to the form to enable a print preview function.|
Double-click on the Print (not the Preview) button and modify the button’s Click event handler as shown below:
Private Sub btnPrint_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnPrint.Click, btnPreview.Click Dim printDoc As New PrintDocument() AddHandler printDoc.BeginPrint, _ New PrintEventHandler(AddressOf Me._beginPrint) AddHandler printDoc.PrintPage, _ New PrintPageEventHandler( _ AddressOf Me._printPage)
If CType(sender, Button).Text = "Preview" Then '---show preview--- Dim dlg As New PrintPreviewDialog() dlg.Document = printDoc dlg.ShowDialog() Else printDoc.Print() End If End Sub
? Figure 9. Preview in Action: By clicking on the new Preview button, you can see a preview of an employee record before sending it to the printer.
Note the addition of the btnPreview.Click event to the Handles clause, which lets you use the same subroutine that services the Print button Click event. At runtime, the code checks to see which button was clicked. If the user clicked the Preview button, the code shows a preview of the printout using the PrintPreviewDialog class. Otherwise, it sends the output to the default printer.
|Figure 10. Printing Multiple Employees: Add the new controls shown in this figure to allow range printing.|
Figure 9 shows what the print preview looks like. It lets you send the printout to the printer or zoom in/out on the preview.
|Author’s Note: When you click on the printer icon in the Print Preview window, the event handlers for the PrintDocument class fire again. Be sure to initialize all variables used for printing whenever these events fire.|
Printing Multiple Pages
You can now preview the printout before actually sending the output to the printer. What if you want to print the name cards several employees at once? To do this, add a few more controls as shown in Figure 10.
To print a specific number of records, users simply need to enter a range. For example, Figure 11 shows a user who wants to print employees 2 and 3.
|Figure 11. Selecting an Employee Range: Using the new controls, specify the range of records you want to print.|
To support printing a specific range of records, modify the BeginPrint event handler:
Private Sub _beginPrint( _ ByVal sender As Object, _ ByVal e As PrintEventArgs) '---initialize the fonts--- f_employeename = New Font( _ "Arial", 10, FontStyle.Bold) f_title = New Font("Arial", 8) f_DOB = New Font("Arial", 8, FontStyle.Italic) '---set to the starting position to print--- EmployeesBindingSource.Position = _ CInt(txtFrom.Text) - 1 End Sub
The preceding code sets the EmployeesBindingSource control to display the first record in the specified range. You then need to modify the PrintPage event handler so that after printing a record, you navigate to the next record (until reaching the last record in the range). When you set the HasMorePages property in the PrintPageEventArgs class to True, the PrintPage event fires repeatedly until you set the property to False.
Code the PrintPage event handler as follows:
Private Sub _printPage( _ ByVal sender As Object, _ ByVal e As PrintPageEventArgs) Dim g As Graphics = e.Graphics Dim sizeOfString As SizeF Try ...
If EmployeesBindingSource.Position + 1 < _ CInt(txtTo.Text) Then e.HasMorePages = True EmployeesBindingSource.MoveNext() Else e.HasMorePages = False End If Catch ex As Exception MsgBox(ex.ToString) End Try End Sub
? Figure 12: Previewing Multiple Pages: Here's the preview of a range of documents that have been selected for printing.
Figure 12 shows the preview of the printout. You can preview all the pages by using the arrows on the top right corner of the Preview window.
Allowing Printer Selection
If you have multiple printers connected to your computer, it makes sense for the user to choose a particular printer to print to instead of sending it directly to the default printer. You can do so using the PrintDialog class.
Modify the Else clause near the end of the Print button's Click event to show a dialog window that allows users to choose a printer.
Private Sub btnPrint_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnPrint.Click, btnPreview.Click Dim printDoc As New PrintDocument() AddHandler printDoc.BeginPrint, _ New PrintEventHandler(AddressOf Me._beginPrint) AddHandler printDoc.PrintPage, _ New PrintPageEventHandler( _ AddressOf Me._printPage) If CType(sender, Button).Text = "Preview" Then '---show preview--- Dim dlg As New PrintPreviewDialog() dlg.Document = printDoc dlg.ShowDialog()
Else Dim pd As New PrintDialog pd.Document = printDoc pd.AllowSomePages = True Dim result As DialogResult = pd.ShowDialog() If result = Windows.Forms.DialogResult.OK Then printDoc.Print() End If End If End Sub
? Figure 13. Picky Printing: Having added print selection functionality, the user can now can select a printer using the PrintDialog class.
Figure 13 shows the Print dialog window when the user clicks on the Print button.
There are several properties that allow you to extract the settings selected in the Print window (such as print range, copies, etc). The following shows some useful properties:
- pd.PrinterSettings.Copies?Number of copies to print
- pd.PrinterSettings.FromPage?The first page to print
- pd.PrinterSettings.ToPage?The last page to print
The Fine Print
In this article, you have seen how simple it is to print from your Windows-based application. The PrintDocument class encapsulates many of the gory details involved in printing from your application, letting you concentrate on the PrintPage event, where you write the logic to format your output and it to the printer. With this newfound knowledge, you have no more excuses for building applications that lack printing support!