I‘ve not blogged in awhile, and I miss sharing with others about all things JavaFX (My day job and family are likely excuses). For those who are new to this blog, I am the author of JavaFX 2 Introduction by Example (JIBE), co-author of Java 7 Recipes, and technical reviewer of the Pro JavaFX 2 books from Apress publishing. For those who already know me, I’d like to thank you for supporting me and the other authors by purchasing these books. More importantly, my hope is to reach out to Java enthusiasts and share ideas.
The book JavaFX 2 Introduction by Example, was published in Nov. 2011 and many more APIs were added since then. During the writing of the book, I was working on the early editions of JavaFX 2.0 up until the announcement at JavaOne Oct. 2011. It was pretty crazy trying to update the book based on API changes as things were almost set in stone. I thought it was amazing how it even got out the door. However, I was pretty pleased. Some of you who have read the beginning of the book (JIBE) understand that the chapters of JIBE are also found in the book Java 7 Recipes (actually it is originally taken from Java 7 recipes). This little fact explains why the book, JavaFX 2 Introduction by Example, is reminiscent of recipe or cookbook style technical books. My intent was to help the reader get introduced quickly without a lot of tech blather. Instead of trying to convince people about the JavaFX platform, I’d rather demonstrate things with useful examples. I find it counter productive discussing deep philosophical debates regarding why one particular technology is superior to the other (cheesy 80’s Highlander reference).
After the release of JavaFX 2.0, there came subsequent versions such as JavaFX 2.1, 2.2 and the upcoming release of JavaFX 8 (January 2014). In this blog entry, I will provide a recipe for the JavaFX 8’s Printing API. Similar to my book (JIBE), I will follow the same pattern as before where I present a problem, solution, code, and a “How it Works” section.
Declaimer: In this blog you will encounter Java Functional Interfaces using Lambda expressions. I will not be discussing them here, but will refer you to Oracle’s tutorials on Project Lambda .
Prerequisite software:
JDK 8 – https://jdk8.java.net/download.html
Problem
You want to create a JavaFX application that prints out a visited website.
Solution
Use the JavaFX 8 PrintJob and Printer APIs to print any JavaFX scene graph node. Also, use the WebView and WebEngine APIs to display a Website or Web page.
Instructions
Assuming you’ve compiled and have run the application, follow the instruction below:
- Enter website address or url into the text field.
- Hit the enter key
- After the page is loaded, click on the “Print” button
- Go to the printer to get the printed web page
Code
package org.carlfx; import javafx.application.Application; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.concurrent.Worker.State; import javafx.print.*; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.transform.Scale; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; /** * Demo to use JavaFX 8 Printer API. * * @author cdea */ public class PrintDemo extends Application { @Override public void start(Stage primaryStage) { final TextField urlTextField = new TextField(); final Button printButton = new Button("Print"); final WebView webPage = new WebView(); final WebEngine webEngine = webPage.getEngine(); HBox hbox = new HBox(); hbox.getChildren().addAll(urlTextField, printButton); BorderPane borderPane = new BorderPane(); borderPane.setTop(hbox); borderPane.setCenter(webPage); Scene scene = new Scene(borderPane, 300, 250); primaryStage.setTitle("Print Demo"); primaryStage.setScene(scene); // print button pressed, page loaded final BooleanProperty printButtonClickedProperty = new SimpleBooleanProperty(false); final BooleanProperty pageLoadedProperty = new SimpleBooleanProperty(false); // when the a page is loaded and the button was pressed call the print() method. final BooleanProperty printActionProperty = new SimpleBooleanProperty(false); printActionProperty.bind(pageLoadedProperty.and(printButtonClickedProperty)); // WebEngine updates flag when finished loading web page. webEngine.getLoadWorker() .stateProperty() .addListener( (ChangeListener) (obsValue, oldState, newState) -> { if (newState == State.SUCCEEDED) { pageLoadedProperty.set(true); } }); // When user enters a url and hits the enter key. urlTextField.setOnAction( aEvent -> { pageLoadedProperty.set(false); printButtonClickedProperty.set(false); webEngine.load(urlTextField.getText()); }); // When the user clicks the print button the webview node is printed printButton.setOnAction( aEvent -> { printButtonClickedProperty.set(true); }); // Once the print action hears a true go print the WebView node. printActionProperty.addListener( (ChangeListener) (obsValue, oldState, newState) -> { if (newState) { print(webPage); } }); primaryStage.show(); } /** Scales the node based on the standard letter, portrait paper to be printed. * @param node The scene node to be printed. */ public void print(final Node node) { Printer printer = Printer.getDefaultPrinter(); PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT); double scaleX = pageLayout.getPrintableWidth() / node.getBoundsInParent().getWidth(); double scaleY = pageLayout.getPrintableHeight() / node.getBoundsInParent().getHeight(); node.getTransforms().add(new Scale(scaleX, scaleY)); PrinterJob job = PrinterJob.createPrinterJob(); if (job != null) { boolean success = job.printPage(node); if (success) { job.endJob(); } } } /** * The main() method is ignored in correctly deployed JavaFX application. * main() serves only as fallback in case the application can not be * launched through deployment artifacts, e.g., in IDEs with limited FX * support. NetBeans ignores main(). * * @param args the command line arguments */ public static void main(String[] args) { launch(args); } }
How it Works
The code begins by creating a TextField, a Button, and a WebView control to be placed into a BorderPane. When using the BorderPane layout, you will be able to place controls into the following regions: Top, Right, Left, Bottom, and Center.
Similar to a web browser, the text field allows the user to enter a website url. Once the url is entered, the user will hit the enter key to load the web page into the WebView node. When placing controls on any of the side regions, the BorderPane layout will take on the preferred height of any controls that are added. The center region will allow a node to take up the available space minus the remaining space taken up by the width and height of the bordering side regions. In other words, if the side regions contain no nodes (empty), a node in the center region has the opportunity to take all the available width and height space provided by its parent (Scene). Since the WebView node will occupy the center region, it will take all the available width and height (minus top region) when the web page is fully loaded. You’ll also notice scroll bars allowing the user to view pages larger than the current view port.
After laying out all the components for the UI, you will need to wire things up. Here you will simply create three boolean property
(javafx.beans.property.SimpleBooleanProperty) instances. The first property variable printButtonClickedProperty is a flag indicating when the print button was clicked. The second property pageLoadedProperty is a flag indicating that the web page was finished loading. Lastly, you will want to note the printActionProperty which binds the printButtonClickedProperty and the pageLoadedProperty by using the fluent API. As they evaluate, the printActionProperty will be true if both the printLoadedProperty and the printLoadedProperty are true values.
// print button pressed, page loaded final BooleanProperty printButtonClickedProperty = new SimpleBooleanProperty(false); final BooleanProperty pageLoadedProperty = new SimpleBooleanProperty(false); // when the a page is loaded and the button was pressed call the print() method. final BooleanProperty printActionProperty = new SimpleBooleanProperty(false); printActionProperty.bind(pageLoadedProperty.and(printButtonClickedProperty));
Continuing the wiring up of the UI, I took an event driven approach where the handler code will respond to events and property changes. Starting with the WebView node, I attached handler code to the statePropery instance (ChangeListener) in order that the pageLoadedProperty will be set to true once the web page is loaded successfully.
// WebEngine updates flag when finished loading web page. webEngine.getLoadWorker() .stateProperty() .addListener( (ChangeListener) (obsValue, oldState, newState) -> { if (newState == State.SUCCEEDED) { pageLoadedProperty.set(true); } });
Next, you will see the text field’s ‘setOnAction‘ method containing handler code that resets the pageLoadedProperty and printButtonClickedProperty objects. Also, the code will initiate the loading of the page via the WebView‘s WebEngine load() method.
// When user enters a url and hits the enter key. urlTextField.setOnAction( aEvent -> { pageLoadedProperty.set(false); printButtonClickedProperty.set(false); webEngine.load(urlTextField.getText()); });
After the TextField control’s action code is wired up, the print button will also need handler code to set the printButtonClickedProperty flag to true. Lastly, the printActionProperty property will need a ChangeListener to respond when its state evaluates to true. When this evaluates to true, my print() method is invoked.
// When the user clicks the print button the webview node is printed printButton.setOnAction( aEvent -> { printButtonClickedProperty.set(true); }); // Once the print action hears a true go print the WebView node. printActionProperty.addListener( (ChangeListener) (obsValue, oldState, newState) -> { if (newState) { print(webPage); } });
Finally, the print() method takes a JavaFX Node object to be printed. The Printer object has a method which returns the default printer your computer is set to. Before actually printing, we can derive a default page layout to scale the node before printing the node. If you don’t do this, only part of the web page will be printed. With the default printer obtained, the createPrinterJob() method is invoked to return a PrinterJob instance that does the actual printing. To print a JavaFX displayable type node, you simply invoke the PrinterJob object’s printPage() method by passing in the Node instance as a parameter.
/** Scales the node based on the standard letter, portrait paper to be printed. * @param node The scene node to be printed. */ public void print(final Node node) { Printer printer = Printer.getDefaultPrinter(); PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT); double scaleX = pageLayout.getPrintableWidth() / node.getBoundsInParent().getWidth(); double scaleY = pageLayout.getPrintableHeight() / node.getBoundsInParent().getHeight(); node.getTransforms().add(new Scale(scaleX, scaleY)); PrinterJob job = PrinterJob.createPrinterJob(); if (job != null) { boolean success = job.printPage(node); if (success) { job.endJob(); } } }
In conclusion, I find that the APIs are simpler to use compared to Java Swing/AWT APIs. I would like to mention that there are many features you can play around with since this blog entry only scratches the surface on the APIs currently available.
NOTE: JavaFX 8 printer API is still in the early stages and there are still outstanding issues (Jira issues).
Please visit to vote and help test functionality at https://javafx-jira.kenai.com
Outstanding Tickets: http://bit.ly/1dw7Vc8
Hi … please help me in this i have this jdk with me jdk-8-ea-bin-b98-windows-i586-11_jul_2013 and it shows me the error on this line
webEngine.getLoadWorker()
.stateProperty()
.addListener( (ChangeListener) (obsValue, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
pageLoadedProperty.set(true);
}
});
I think my jdk doesnot supports lambda expression….. 😦
Tilakraj,
When compiling the source code on your machine, are you using the command line or using an IDE?
If you’re using the command line to compile, please run java -version to make sure it’s the correct JDK.
If you’re using an IDE to compile and execute, be sure to check your project settings for the correct JDK (source code lambda expressions).
I hope that helps.
Carl
Pingback: JavaFX links of the week, July 22 // JavaFX News, Demos and Insight // FX Experience
Pingback: Java desktop links of the week, July 22 « Jonathan Giles
Pingback: What’s new in Java 8 (Part I – JavaFX) – Pixel Duke
Pingback: New features in next major release – Java FX 8.0 | Java FX Tutorials
Wow… happe to hear that printing support will be available.
I copied and pasted and ran it but when I clicked the print button, there was no reaction or print the content I wanted to print.
How is the program suppose to respond upon clicking the print button?
Thanks.
I struggled with this also. You must enter the URL and hit the enter key to load the page and that will set the pageLoadedProperty
I get the page to load but when I press the button this code never fires. this code gives an error on newState (cannot convert from Object to boolean). I fixed by casting to boolean.
printActionProperty.addListener( (ChangeListener) (obsValue, oldState, newState) -> {
if (newState) {
print(webPage);
}
});
primaryStage.show();
}
Reblogged this on Life of a Geek- Technology freak...
How do you go about if the panel you’re printing exceeds one page?
What’s happening to me right now the page get cut if longer than 1 page
Chux’s question is quite essential. I would also need something like the old java.awt.print.Pageable or some “function” that is capable of page-breaking and as such divides my HTML into different (HTML-)snippets fitting onto one page each (i.e. given size, using predefined resolution). Any idea how that could be achieved?
Another thing: Is there a way to render a JavaFX-Node onto a classic Java Graphics2D object? I’m in a hybrid environment (Swing/JavaFX) and thus dependent on the old printing mechanism. The only way I have seen is to create a PrismPrintGraphics object from the given Graphics2D and then use the render method of a com.sun.javafx.sg.prism.NGNode which can be received by calling webView.impl_getPeer(). However, that method is marked as deprecated. Or is the key using a JFXPanel (methods #paint(…)/#print(…))? Is that working in a headless environment?
Probably the last (stupid) question: Setters of the whole printing environment use to be private, package local or simply not available. So how do I for example create my own PageLayout with special paper size and self-defined margins (Printer.MarginType)?
I’ve tried this print example and experimented a bit and found something strange.
When I use a transform the print quality appears to drop (appears blurred), if I don’t use a transform then the output is clear and crisp.
I have attempted to print without setting a scale transform but used a translate in order to centre the content on the page and I see the same lose of quality so it doesn’t appear to be only if using the scale transform. It seems to happen for any transform, once removed the output appears much clearer.
I wondered if anyone else is seeing this or know how to prevent it.
Look at snapshot() or PixelWriter
Thanks!
Hi Carl, is there a way to contact you, like a email, I’m on chapter 4 of you’r book “Java FX by example” but can’t get the Listing 4-19 to work. I wirited the code directly from the book and didn’t work. Later on downloaded you’r code from Apress site, with the same result. Will you please give me a hint? Sorry if this is not the place for this comment. Sincerely, Pedro.
Pedro,
Could you tell me what operating system, Java version, IDE, and Exception/error that you are encountering?
If you are using gedit, notepad, Nano or Vim make sure you read chapter 1 pg 23-25 on how to compile and run examples.
Carl
Hello, you can print only part dela page, or get the value of any item?
Pedro,
So sorry I didn’t get to you.
Can you email me at:
carldea at yahoo.com