Introduction by Example: JavaFX 8 Printing

15 07 2013

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:

  1. Enter website address or url into the text field.
  2. Hit the enter key
  3. After the page is loaded, click on the “Print” button
  4. 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);
    }
}
Print Demo using JavaFX 8

Print Demo using JavaFX 8

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

About these ads

Actions

Information

15 responses

18 07 2013
Tilakraj Gandhi

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….. :(

18 07 2013
carldea

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

21 07 2013
JavaFX links of the week, July 22 // JavaFX News, Demos and Insight // FX Experience

[…] Dea has a post about the printing support coming up in JavaFX […]

21 07 2013
Java desktop links of the week, July 22 « Jonathan Giles

[…] Dea has a post about the printing support coming up in JavaFX […]

22 08 2013
What’s new in Java 8 (Part I – JavaFX) – Pixel Duke

[…] Printing support will be added to JavaFX. You can check this detailed blog post by Carl Dea for more details: http://carlfx.wordpress.com/2013/07/15/introduction-by-example-javafx-8-printing/ […]

28 08 2013
New features in next major release – Java FX 8.0 | Java FX Tutorials

[…] A simpler printer API will be released as part of 8.0 compared to Swing printing API. Java FX printing API is capable of printing any Java FX node, includes Web View nodes, with appropriate scaling. A detailed tutorial can be found here:  http://carlfx.wordpress.com/2013/07/15/introduction-by-example-javafx-8-printing/ […]

1 09 2013
TPC

Wow… happe to hear that printing support will be available.

26 11 2013
iPoone

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.

23 03 2014
Jeff Spencer

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

23 03 2014
Jeff Spencer

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();
}

24 04 2014
ShantanuChandra
26 04 2014
Chux

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

24 06 2014
Mr Pink

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?

24 06 2014
Mr Pink

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?

24 06 2014
Mr Pink

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)?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Follow

Get every new post delivered to your Inbox.

Join 74 other followers

%d bloggers like this: