javafxfxml

Printing multiple AnchorPanes on their own pages


I have a javafx TabPane that I want to be able to print. The TabPane has two tabs, and I want each tab to be printed on its own page. I created a button on one of the tabs with the onAction function to run a method called onPrint. The button will print a table within one tab and another table in the other.

The printer.fxml file

<ScrollPane fx:id="assessment"/>
    <Pane fx:id="printPane">
    <TabPane prefHeight="900.0" prefWidth="937.0" tabClosingPolicy="UNAVAILABLE">
        <Tab text="Issues">
                <AnchorPane fx:id="issuesTab" minHeight="0.0" minWidth="0.0" prefHeight="519.0" prefWidth="585.0">
                 <Text text="here is some text" />
                 <Button fx:id="print" layoutX="82.0" layoutY="60.0" mnemonicParsing="false" onAction="#onPrint" text="print" /> 
                </AnchorPane>
        </Tab>
        <Tab text="Function">
                <AnchorPane fx:id="FunctionTab" prefHeight="400.0" prefWidth="600.0">
                     <Text text="here is some text"/>
                </AnchorPane>
        </Tab>
</Pane>
</Scrollpane>

I've tried several different ways to get this to work properly. I'll walk through them below.

The printerController file is as follows:

public ScrollPane assessment; 
public Pane printerPane; 
public AnchorPane issuesTab;
public AnchorPane functionTab;


 public void onPrint(ActionEvent event) {
        printPane.getChildren().addAll(issuesTab, functionTab);
        Printer printer = Printer.getDefaultPrinter();
        PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, Printer.MarginType.HARDWARE_MINIMUM);
        PrinterJob job = PrinterJob.createPrinterJob();
        job.getJobSettings().setPageRanges(new PageRange(1, 2));
        if (job.showPrintDialog(printPane.getScene().getWindow())){
            boolean success = job.printPage(pageLayout, printPane);
            if (success) {
                job.endJob();
            }
        }
    }

Currently this works but it adds both tables ontop of each other. I've tried to use the setPageRanges function to set a page range but I cannot get it to work propertly.

I then thought maybe I need to create a brand new pane for each tab and then add those to a group and it'll print separately.

    private Node toPrint;

    private Node printAllPages(){
        if(toPrint== null){
            Group group = new Group();
            group.getChildren().addAll(
                    new Pane(issuesTab),
                    new Pane(functionTab)
            );
            notToPrint = group;
        }
        return notToPrint;
    }
 public void onPrint(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();
        if(job.showPrintDialog(notToPrint.getScene().getWindow())){
            boolean success = job.printPage(printAllPages());
            if(success){
                job.endJob();
            }
        }


    }

However this results in the following error - Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.Node.getScene()" because "this.notToPrint" is null. I also don't believe this is the right approach.

Are there any Printer, PageLayout or PrinterJob settings that I need to apply so that each AnchorPane can be printed on separate pages?


Edits made:

Per @SedJ601 advice on how to fix this issue. I went through the link he provided and decided to try this -

PrintController.java

    public TabPane allTabs;
   Printer printer = Printer.getDefaultPrinter();
        PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT);
        PrinterJob job = PrinterJob.createPrinterJob();

        if(job.showPrintDialog(allTabs.getScene().getWindow())){
            double pagePrintableHeight = pageLayout.getPrintableHeight();
            double pagePrintableWidth = pageLayout.getPrintableWidth();
            double scaleX = pagePrintableWidth / allTabs.getBoundsInParent().getWidth();
            double localScale = scaleX;


            double numberOfPages = Math.ceil((allTabs.getPrefWidth()) * localScale) / pagePrintableHeight;
            System.out.println(numberOfPages);

            for(int i = 0; i < numberOfPages; i++){ job.printPage(pageLayout, allTabs.get(i)); } job.endJob();
        }else{
            System.out.println("This did not work");
        }

Print.fxml

<ScrollPane fx:id="assessment">
<TabPane fx:id="allTabs" prefHeight="900.0" prefWidth="937.0" tabClosingPolicy="UNAVAILABLE">
    <Tab text="Issues">
        <content>
            <AnchorPane fx:id="issuesTab" minHeight="0.0" minWidth="0.0" prefHeight="519.0" prefWidth="585.0">
             <Text text="some text" />
            </AnchorPane>
        </content>
    </Tab>
    <Tab text="Function">
        <content>
            <AnchorPane fx:id="functionTab" prefHeight="400.0" prefWidth="600.0">
               <Text text="some text" />
            </AnchorPane>
        </content>
    </Tab>
</TabPane>

However this is printing only the first tab. I tried setting the numberOfPages value equal to 10.0 and still only the first tab prints. How do I edit either the for loop to loop through both tabs or change the numberOfPages to accurately find all pages?


Solution

  • If you are going to only have two Tabs then I think the following code should work.

    public void onPrint(ActionEvent event) {
            printPane.getChildren().addAll(issuesTab, functionTab);
            Printer printer = Printer.getDefaultPrinter();
            PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, Printer.MarginType.HARDWARE_MINIMUM);
            PrinterJob job = PrinterJob.createPrinterJob();
            job.getJobSettings().setPageRanges(new PageRange(1, 2));
            if (job.showPrintDialog(printPane.getScene().getWindow())){
                //if the number of tabs is variable use a loop and print each tab.
                boolean success = job.printPage(pageLayout, issuesTab);
                boolean successTwo = job.printPage(pageLayout, functionTab);
                if (success && successTwo) {
                    job.endJob();
                }
                else{
                    System.out.println("Printing Error!");
               }
            }
        }
    

    I typed the above code in here without testing it. It may have errors.

    Here is a MCVE.

    //Original code from https://jenkov.com/tutorials/javafx/tabpane.html
    import javafx.application.Application;
    import javafx.collections.ObservableList;
    import javafx.print.PageLayout;
    import javafx.print.PageOrientation;
    import javafx.print.Paper;
    import javafx.print.Printer;
    import javafx.print.PrinterJob;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TabPane;
    import javafx.scene.control.Tab;
    import javafx.scene.control.Label;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class App extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {       
            TabPane tabPane = new TabPane();
            VBox.setVgrow(tabPane, Priority.ALWAYS);
            
            StackPane stackPane1 = new StackPane(new Label("This is tab 1"));
            StackPane stackPane2 = new StackPane(new Label("This is tab 2"));
            StackPane stackPane3 = new StackPane(new Label("This is tab 3"));
    
            Tab tab1 = new Tab("Tab 1", stackPane1);
            Tab tab2 = new Tab("Tab 2"  , stackPane2);
            Tab tab3 = new Tab("Tab 3" , stackPane3);
    
            tabPane.getTabs().add(tab1);
            tabPane.getTabs().add(tab2);
            tabPane.getTabs().add(tab3);
            
            VBox root = new VBox(tabPane);
            Scene scene = new Scene(root, 500, 500);
            
            Button button = new Button("Print Nodes");
            button.setOnAction(actionEvent -> {
                printNodes(tabPane.getTabs(), scene);
            });        
            root.getChildren().add(button);
            
            primaryStage.setScene(scene);
            primaryStage.setTitle("JavaFX App");
    
            primaryStage.show();
        }
        
        
        public void printNodes(ObservableList<Tab> nodesFromSameScene, Scene scene)
        {
            boolean control = true;
            
            Printer printer = Printer.getDefaultPrinter();
            PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, Printer.MarginType.HARDWARE_MINIMUM);
            PrinterJob job = PrinterJob.createPrinterJob();
            
            if (job.showPrintDialog(scene.getWindow()))
            {
                for(int i = 0; i < nodesFromSameScene.size(); i++)
                {
                    if(!job.printPage(pageLayout, nodesFromSameScene.get(i).getContent()))
                    {
                        control = false;
                        break;
                    }            
                }
    
                if(control)
                {
                    job.endJob();
                }
                else
                {
                    System.out.println("Printing Error!");
                }
            }        
        }
    }
    

    Output - Saved as PDF instead of printing!

    enter image description here