How to show the page setup and print dialog to users
How to customize the setting for the printer jobs
How to set up the page layout for printing
How to print web pages displayed in a WebView
The examples of this chapter lie in the com.jdojo.print package. In order for them to work, you must add a corresponding line to the module-info.java file:
...
opens com.jdojo.print to javafx.graphics, javafx.base;
...
What Is the Print API?
JavaFX includes support for printing nodes through the Print API in the javafx.print package. The API consists of the following classes and a number of enums (not listed):
Printer
PrinterAttributes
PrintResolution
PrinterJob
JobSettings
Paper
PaperSource
PageLayout
PageRange
Instances of the above-listed classes represent different parts of the printing process. For example, a Printer represents a printer that can be used for printing jobs; a PrinterJob represents a print job that can be sent to a Printer for printing; and a Paper represents the paper sizes available on printers.
The Print API provides support for printing nodes that may or may not be attached to a scene graph. It is a common requirement to print the content of a web page, not the WebView node that contains the web page. The javafx.scene.web.WebEngine class contains a print(PrinterJob job) method that prints the contents of the web page, not the WebView node.
If a node is modified during the printing process, the printed node may not appear correct. Note that the printing of a node may span multiple pulse events resulting in concurrent change in the content being printed. To ensure correct printing, please make sure that the node being printed is not modified during the print process.
Nodes can be printed on any thread including the JavaFX Application Thread. It is recommended that large, time-consuming print jobs be submitted on a background thread to keep the UI responsive.
Classes in the Print API are final as they represent existing printing device properties. Most of them do not provide any public constructor as you cannot make up a printing device. Rather, you obtain their references using factory methods in various classes.
Note
The Print API provides the basic printing support only to print nodes and web pages. You will not be able to use it to print reports in JavaFX applications.
Listing Available Printers
The Printer.getAllPrinters() static method returns an observable list of installed printers on the machine. Note that the list of printers returned by the method may change over time as new printers are installed or old printers are removed. Use the getName() method of the Printer to get the name of the printer. The following snippet of code lists all installed printers on the machine running the code. You may get a different output:
The Printer.getDefaultPrinter() method returns the default Printer. The method may return null if no printer is installed. The default printer may be changed on a machine. Therefore, the method may return different printers from call to call, and the printer returned may not be valid after some time. The following snippet of code shows how to get the default printer:
Printing a node is easy: create a PrinterJob and call its printPage() method passing the node to be printed. Printing a node using the default printer with all default settings takes only three lines of code:
System.out.println("Could not create a printer job.");
}
You can use the createPrinterJob() static method of the PrinterJob class to create a printer job:
public static PrinterJob createPrinterJob()
public static PrinterJob createPrinterJob(Printer printer)
The method with no-args creates a printer job for the default printer. You can use the other version of the method to create a printer job for the specified printer.
You can change the printer for a PrinterJob by calling its setPrinter() method. If the current printer job settings are not supported by the new printer, the settings are reset automatically for the new printer:
// Set a new printer for the printer job
printerJob.setPrinter(myNewPrinter);
Setting a null printer for the job will use the default printer.
Use one of the following printPage() methods to print a node:
The first version of the method takes only the node to be printed as the parameter. It uses the default page layout for the job for printing.
The second version lets you specify a page layout for printing the node. The specified PageLayout will override the PageLayout for the job, and it will be used only for printing the specified node. For subsequent printing, the default PageLayout for the job will be used. You can create a PageLayout using the Printer class. I will discuss an example of this kind later.
The printPage() method returns true if the printing was successful. Otherwise, it returns false. When you are done printing, call the endJob() method. The method returns true if the job can be successfully spooled to the printer queue. Otherwise, it returns false, which may indicate that the job could not be spooled or it was already completed. After successful completion of the job, the job can no longer be reused.
Tip
You can call the printPage() method on a PrinterJob as many times as you want. Calling the endJob() method tells the job that no more printing will be performed. The method transitions the job status to DONE, and the job should no longer be reused.
You can cancel a print job using the cancelJob() method of the PrinterJob. The printing may not be cancelled immediately, for example, when a page is in the middle of printing. The cancellation occurs as soon as possible. The method does not have any effect if
The job has already been requested to be cancelled.
The job is already completed.
The job has an error.
A PrinterJob has a read-only status, which is defined by one of the constants of the PrinterJob.JobStatus enum:
NOT_STARTED
PRINTING
CANCELED
DONE
ERROR
The NOT_STARTED status indicates a new job. In this status, the job can be configured, and printing can be initiated. The PRINTING status indicates that the job has requested to print at least one page, and it has not terminated printing. In this status, the job cannot be configured.
The other three statuses, CANCELED, DONE, and ERROR, indicate the termination state of the job. Once the job is in one of these statuses, it should not be reused. There is no need to call the endJob() method when the status goes to CANCELED or ERROR. The DONE status is entered when the printing was successful and the endJob() method was called. The PrinterJob class contains a read-only jobStatus property that indicates the current status of the print job.
The program in Listing 27-1 shows how to print nodes. It displays a TextArea where you can enter text. Two Buttons are provided: one prints the TextArea node and the other the entire scene. When printing is initiated, the print job status is displayed in a Label. The code in the print() method is the same as previously discussed. The method includes the logic to display the job status in the Label. The program displays the window shown in Figure 27-1. Run the program; enter text in the TextArea; and click one of the two buttons to print.
// PrintingNodes.java
package com.jdojo.print;
import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PrintingNodes extends Application {
private Label jobStatus = new Label();
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage stage) {
VBox root = new VBox(5);
Label textLbl = new Label("Text:");
TextArea text = new TextArea();
text.setPrefRowCount(10);
text.setPrefColumnCount(20);
text.setWrapText(true);
// Button to print the TextArea node
Button printTextBtn = new Button("Print Text");
printTextBtn.setOnAction(e -> print(text));
// Button to print the entire scene
Button printSceneBtn = new Button("Print Scene");
printSceneBtn.setOnAction(e -> print(root));
HBox jobStatusBox = new HBox(5,
new Label("Print Job Status:"), jobStatus);
HBox buttonBox = new HBox(5,
printTextBtn, printSceneBtn);
root.getChildren().addAll(
textLbl, text, jobStatusBox, buttonBox);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Printing Nodes");
stage.show();
}
private void print(Node node) {
jobStatus.textProperty().unbind();
jobStatus.setText("Creating a printer job...");
// Create a printer job for the default printer
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
// Show the printer job status
jobStatus.textProperty().bind(
job.jobStatusProperty().asString());
// Print the node
boolean printed = job.printPage(node);
if (printed) {
// End the printer job
job.endJob();
} else {
jobStatus.textProperty().unbind();
jobStatus.setText("Printing failed.");
}
} else {
jobStatus.setText(
"Could not create a printer job.");
}
}
}
Listing 27-1
Printing Nodes
Showing the Page Setup and Print Dialogs
The Print API allows users to interact with the printing process. Users can change the printer settings interactively before the printing is initiated. The API lets you show Page Setup and Print Setup dialogs for setting the page properties and printer settings for the job.
You can let the user configure the page layout by showing a Page Setup dialog. Use the showPageSetupDialog(Window owner) method of the PrinterJob to show a Page Setup dialog. The user can set the page size, source, orientation, and margin. The dialog may allow the user to access other printing properties such as the list of printers. Once the user confirms the settings on the dialog, the PrinterJob has the new settings. The method returns true if the user confirms the settings on the dialog. It returns false if the user cancels the dialog. It also returns false if the dialog cannot be displayed, such as when the job is not in the NOT_STARTED state.
The owner parameter to the method is the window that will be the owner of the dialog box. It can be null. If specified, the inputs to the window will be blocked, while the dialog is displayed:
PrinterJob job = PrinterJob.createPrinterJob();
// Show the page setup dialog
boolean proceed = job.showPageSetupDialog(null);
if (proceed) {
// Start printing here or you can print later
}
You can use the showPrintDialog(Window owner) method to show a Print dialog where the user can modify the printer and settings for the PrinterJob. The return value and parameter of this method have meanings similar to that of the showPageSetupDialog() method:
PrinterJob job = PrinterJob.createPrinterJob();
// Show the print setup dialog
boolean proceed = job.showPrintDialog(null);
if (proceed) {
// Start printing here or you can print later
}
The program in Listing 27-2 shows a similar window as shown by the program in Listing 27-1. This time, clicking the print buttons displays a Page Setup and Print Setup dialogs (as shown in Figure 27-2). Once the user confirms the settings on the dialogs, the text in the TextArea is printed. Notice that even though you create a PrinterJob for the default printer before showing the dialogs, you can change the printer using the dialogs, and the text will print using the changed printer.
// PrintDialogs.java
// ...find in the book's download area
Listing 27-2
Showing the Page Setup and Print Dialogs to the User
Customizing PrinterJob Settings
The Print API contains two classes that are related to printer and printer job settings:
PrinterAttributes
JobSettings
A printer has attributes, which indicate the printing capabilities of the printer. Examples of printer attributes are default paper size, supported paper sizes, maximum number of copies, and default collation. A PrinterAttributes object encapsulates the attributes of a printer. The Print API does not let you change the printer attributes as you cannot change the capabilities of a printer. You can only use its capabilities. You cannot create a PrinterAttributes object directly. You need to get it from a Printer object using the getPrinterAttributes() method. The following snippet of code prints some attributes of the default printer in the machine. You may get a different output:
A PrinterAttributes is an immutable object. It contains the default and supported attributes of a printer. You obtain PrinterAttributes from a Printer object.
A JobSettings contains the printer attributes to be used for a print job for a specific printer. You can obtain the JobSettings of a print job using the getJobSettings() method of the PrinterJob object. A JobSettings is a mutable object. It contains a property for each printer attribute that can be set for a print job. By default, its properties are initialized to the default properties of the printer. You can change the property that will be used for the current print job. If you change the property of a JobSettings that is not supported by the printer, the property reverts to the default value for the printer. The following snippet of code sets the printSides property to DUPLEX. In this case, the printer supports only ONE_SIDED printing. Therefore, the printSides property is set to ONE_SIDED, which is the default, and only supported printSides value by the printer. You may get a different output:
// Create a printer job for the default printer
PrinterJob job = PrinterJob.createPrinterJob();
// Get the JobSettings for the print job
JobSettings jobSettings = job.getJobSettings();
System.out.println(jobSettings.getPrintSides());
// Set the printSides to DUPLEX
jobSettings.setPrintSides(PrintSides.DUPLEX);
System.out.println(jobSettings.getPrintSides());
ONE_SIDED
ONE_SIDED
For a print job, you can specify the page ranges using the pageRanges property of the JobSettings. The pageRanges property is an array of PageRange. A PageRange has startPage and endPage properties that define the range. The following snippet of code sets the page ranges for a job to 1–5 and 20–25:
PrinterJob job = PrinterJob.createPrinterJob();
JobSettings jobSettings = job.getJobSettings();
jobSettings.setPageRanges(new PageRange(1, 5), new PageRange(20, 25));
Most of the printer attributes are represented by enum constants. For example, the collation attribute is represented by Collation.COLLATED and Collation.UNCOLLATED constants. Some attributes, such as the number of copies to be printed, are specified as an int. Please refer to the list of properties in the JobSettings class that you can set for a print job.
Setting Page Layout
An instance of the PageLayout class represents the page setup for a print job. By default, it is set to the printer default value. You have already seen setting up the page layout using the Page Setup dialog. A PageLayout encapsulates three things:
The paper size
The page orientation
The page margins
A PageLayout is used to configure the printable area of the page, which must lie within the printable area of the hardware. If a page is rendered outside the printable area of the hardware, the content is clipped.
You cannot create a PageLayout object directly. You need to use one of the createPageLayout() methods of the Printer to get a PageLayout:
The margins can be specified as numbers or as one of the following constants of the Printer.MarginType enum:
DEFAULT
EQUAL
EQUAL_OPPOSITES
HARDWARE_MINIMUM
The DEFAULT margin type requests default 0.75 inch on all sides.
The EQUAL margin type uses the largest of the four hardware margins on all four sides, so the margins are equal on all four sides.
The EQUAL_OPPOSITES margin type uses the larger of left and right hardware margins for the left and right sides, and the larger of the top and bottom hardware margins for the top and bottom sides.
The HARDWARE_MINIMUM requests that the minimum hardware allowed margins should be set on all sides.
The following snippet of code creates a PageLayout for A4 size paper, LANDSCAPE page orientation, and equal margins on all sides. The PageLayout is set to a print job:
Sometimes, you want to know the size of the printable area on the page. You can get it using the getPrintableWidth() and getPrintableHeight() methods of the PageLayout. This is useful if you want to resize a node before printing, so it fits the printable area. The following snippet of code prints an Ellipse that fits the printable area:
// Make the Ellipse fit the printable are of the page
Ellipse node = new Ellipse(pgW/2, pgH/2, pgW /2, pgH/2);
node.setFill(null);
node.setStroke(Color.BLACK);
node.setStrokeWidth(1);
boolean printed = job.printPage(node);
if (printed) {
// End the printer job
job.endJob();
}
Printing a Web Page
There is a special way to print the contents of a web page. Use the print(PrinterJob job) method of the WebEngine class to print the web page loaded by the engine. The method does not modify the specified job. The job can be used for more printing after the print() method call:
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
...
PrinterJob job = PrinterJob.createPrinterJob();
webEngine.print(job);
The program in Listing 27-3 shows how to print web pages. There is nothing new in the program that you have not already covered. The program displays a window with a URL field, a Go button, a Print button, and a WebView. The Print button is enabled when a web page is successfully loaded. You can enter a web page URL and click the Go button to navigate to the page. Click the Print button to print the web page.
// PrintingWebPage.java
// ...find in the book's download area.
Listing 27-3
Printing a Web Page
Summary
JavaFX includes support for printing nodes through the Print API in the javafx.print package. The API consists of a few classes and a number of enums. The Print API provides support for printing nodes that may or may not be attached to a scene graph. It is a common requirement to print the content of a web page, not the WebView node that contains the web page. The javafx.scene.web.WebEngine class contains a print(PrinterJob job) method that prints the contents of the web page, not the WebView node.
If a node is modified during the printing process, the printed node may not appear correct. Note that the printing of a node may span multiple pulse events resulting in concurrent change in the content being printed. To ensure correct printing, make sure that the node being printed is not modified during the print process.
Nodes can be printed on any thread including the JavaFX Application Thread. It is recommended that large, time-consuming print jobs be submitted on a background thread to keep the UI responsive.
Classes in the Print API are final as they represent existing printing device properties. Most of them do not provide any public constructor as you cannot make up a printing device. Rather, you obtain their references using factory methods in various classes.
An instance of the Printer class represents a printer. The Printer.getAllPrinters() static method returns an observable list of installed printers on the machine. Note that the list of printers returned by the method may change over time as new printers are installed or old printers are removed. Use the getName() method of the Printer to get the name of the printer.
The Printer.getDefaultPrinter() method returns the default Printer. The method may return null if no printer is installed. The default printer may be changed on a machine. Therefore, the method may return different printers from call to call, and the printer returned may not be valid after some time.
You can create a printer job by calling the PrinterJob.createPrinterJob() method. It returns an object of the PrinterJob class. Once you get a PrinterJob object, call its printPage() method to print a node. The node to be printed is passed as an argument to the method.
The Print API allows users to interact with the printing process. Users can change the printer settings interactively before the printing is initiated. The API lets you show Page Setup and Print Setup dialogs for setting the page properties and printer settings for the job. You can let the user configure the page layout by showing a Page Setup dialog. Use the showPageSetupDialog(Window owner) method of the PrinterJob to show a Page Setup dialog. The user can set the page size, source, orientation, and margins. The dialog may allow the user to access other printing properties such as the list of printers.
The Print API lets you customize the printer job settings. The API contains two classes that are related to printer and printer job settings: PrinterAttributes and JobSettings classes. A printer has attributes, which indicate the printing capabilities of the printer such as default paper size, supported paper sizes, maximum number of copies, and default collation. A PrinterAttributes object encapsulates the attributes of a printer. The Print API does not let you change the printer attributes as you cannot change the capabilities of a printer. You cannot create a PrinterAttributes object directly. You need to get it from a Printer object using the getPrinterAttributes() method.
An instance of the PageLayout class represents the page setup for a print job. By default, it is set to the printer default value. A PageLayout is used to configure the printable area of the page, which must lie within the printable area of the hardware. If a page is rendered outside the printable area of the hardware, the content is clipped. You cannot create a PageLayout object directly. You need to use one of the createPageLayout() methods of the Printer to get a PageLayout.
There is a special way to print the contents of a web page. Use the print(PrinterJob job) method of the WebEngine class to print the web page loaded by the engine. The method does not modify the specified job. The job can be used for more printing after the print() method call.