JavaFX的:打印多个页面

问题描述:

一个节点我试图与在JDK 8JavaFX的:打印多个页面

请看下面的测试程序推出的JavaFX的新的打印API取得联系:

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.ToolBar; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 

public class SimplePrintingTest extends Application { 

    private PrinterJob job = PrinterJob.createPrinterJob(); 

    public static void main(String[] args) { 
    launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) { 
    BorderPane pane = new BorderPane(); 

    final Rectangle rect = new Rectangle(0, 0, 1000, 1000); 
    pane.setCenter(rect); 

    final ToolBar value = new ToolBar(); 

    final Button print = new Button("print"); 
    final Button dialog = new Button("print dialog"); 
    final Button pageLayout = new Button("page layout settings"); 
    value.getItems().add(print); 
    value.getItems().add(dialog); 
    value.getItems().add(pageLayout); 
    print.setOnAction(event -> print(pane)); 
    dialog.setOnAction(event -> showPrintDialog(primaryStage)); 
    pageLayout.setOnAction(event -> showPageSetupDialog(primaryStage)); 

    pane.setTop(value); 
    Scene scene = new Scene(pane, 1200, 1024, Color.GRAY); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
    } 

    public void print(Node node) { 
    if (job != null) {  
     // -- ??? 
     boolean success = job.printPage(node); 
     if (success) { 
     job.endJob(); 
     job = PrinterJob.createPrinterJob(); 
     } 
    } 
    } 

    public void showPageSetupDialog(Stage stage) { 
    if (job != null) { 
     job.showPageSetupDialog(stage); 
    } 
    } 

    public void showPrintDialog(Stage stage) { 
    if (job != null) { 
     job.showPrintDialog(stage); 
    } 
    } 
} 

我的问题现在:如何配置或使用打印机作业来打印对于多个页面中的一页显然太大的场景内容? 我试图设置页面范围这样

job.getJobSettings().setPageRanges(new PageRange(1, 5)); 

或本

job.getJobSettings().setPageRanges(new PageRange(1, 1), new PageRange(2, 2)); 

或更改之间的PrintPage称这样

job.getJobSettings().setPageRanges(new PageRange(1, 1)); 
boolean success = job.printPage(node); 
job.getJobSettings().setPageRanges(new PageRange(2, 2)); 
success &= job.printPage(node); 

,但似乎没有任何工作的页面范围。每次打电话给printPage时,始终只有内容的左半部分在打印文档中可见。要清楚:我不想缩小打印的页面以适应一个页面,我想保留节点的大小并将其完全打印在多个页面上。这在Swing中是可能的。 JavaFX中不再可能吗?

+1

我面临同样的问题,你有没有找到一个解决方案? – user1125516 2015-02-25 20:23:50

+2

是的,我会在我有时间的时候发布解决方案 – Sebastian 2015-02-26 09:00:16

好吧,所以基本上我使用转换来定位节点,以我想要的方式打印每个页面。

首先,类NodePrinter执行实际打印:

import javafx.print.PageLayout; 
import javafx.print.PrinterJob; 
import javafx.scene.Node; 
import javafx.scene.shape.Rectangle; 
import javafx.scene.transform.Scale; 
import javafx.scene.transform.Transform; 
import javafx.scene.transform.Translate; 
import javafx.stage.Window; 

import java.util.ArrayList; 
import java.util.List; 

/** 
* Prints any given area of a node to multiple pages 
*/ 
public class NodePrinter { 

    private static final double SCREEN_TO_PRINT_DPI = 72d/96d; 

    private double scale = 1.0f; 

    /** 
    * This rectangle determines the portion to print in the world coordinate system. 
    */ 
    private Rectangle printRectangle; 

    /** 
    * Prints the given node. 
    * @param job The printer job which has the configurations for the page layout etc. and does the actual printing. 
    * @param showPrintDialog Whether or not the print dialog needs to be shown prior to printing. 
    * @param node The content to print. 
    * @return <code>true</code> if everything was printed, <code>false</code> otherwise 
    */ 
    public boolean print(PrinterJob job, boolean showPrintDialog, Node node) { 

    // bring up the print dialog in which the user can choose the printer etc. 
    Window window = node.getScene() != null ? node.getScene().getWindow() : null; 

    if (!showPrintDialog || job.showPrintDialog(window)) { 

     PageLayout pageLayout = job.getJobSettings().getPageLayout(); 
     double pageWidth = pageLayout.getPrintableWidth(); 
     double pageHeight = pageLayout.getPrintableHeight(); 

     PrintInfo printInfo = getPrintInfo(pageLayout); 

     double printRectX = this.printRectangle.getX(); 
     double printRectY = this.printRectangle.getY(); 
     double printRectWith = this.printRectangle.getWidth(); 
     double printRectHeight = this.printRectangle.getHeight(); 

     // the following is suboptimal in many ways but needed for the sake of demonstration. 
     // there need to be transformations made on the node so we store them and restore them later. 
     // this is bad when the node is embedded somewhere in the scene graph because the size changes 
     // will trigger updates and at least lead to "flickering". 
     // in a real world application there should be another way to construct a node object 
     // specifically for printing. 

     // store old transformations and clip of the node 
     Node oldClip = node.getClip(); 
     List<Transform> oldTransforms = new ArrayList<>(node.getTransforms()); 
     // set the printingRectangle bounds as clip 
     node.setClip(new javafx.scene.shape.Rectangle(printRectX, printRectY, 
      printRectWith, printRectHeight)); 

     int columns = printInfo.getColumnCount(); 
     int rows = printInfo.getRowCount(); 

     // by adjusting the scale, you can force the contents to be printed one page for example 
     double localScale = printInfo.getScale(); 

     node.getTransforms().add(new Scale(localScale, localScale)); 
     // move to 0,0 
     node.getTransforms().add(new Translate(-printRectX, -printRectY)); 

     // the transform that moves the node to fit the current printed page in the grid 
     Translate gridTransform = new Translate(); 
     node.getTransforms().add(gridTransform); 

     // for each page, move the node into position by adjusting the transform 
     // and call the print page method of the PrinterJob 
     boolean success = true; 
     for (int row = 0; row < rows; row++) { 
     for (int col = 0; col < columns; col++) { 
      gridTransform.setX(-col * pageWidth/localScale); 
      gridTransform.setY(-row * pageHeight/localScale); 

      success &= job.printPage(pageLayout, node); 
     } 
     } 
     // restore the original transformation and clip values 
     node.getTransforms().clear(); 
     node.getTransforms().addAll(oldTransforms); 
     node.setClip(oldClip); 
     return success; 
    } 
    return false; 
    } 

    /** 
    * Returns a scale factor to apply for printing. 
    * A value of <code>0.72</code> makes <code>96</code> units in the world coordinate system appear exactly one inch long. 
    * The default value is <code>1.0</code>. 
    */ 
    public double getScale() { 
    return scale; 
    } 

    /** 
    * Sets a scale factor to apply for printing. 
    * A value of <code>0.72</code> makes <code>96</code> units in the world coordinate system appear exactly one inch long. 
    * The default value is <code>1.0</code>. 
    */ 
    public void setScale(final double scale) { 
    this.scale = scale; 
    } 

    /** 
    * Returns the rectangle that will be printed. 
    * This rectangle determines the portion of the node to print in the world coordinate system. 
    * @return a rectangle in the world coordinate system that defines the area of the contents of the 
    *      node to print. 
    */ 
    public Rectangle getPrintRectangle() { 
    return printRectangle; 
    } 

    /** 
    * Sets the rectangle that will be printed. 
    * This rectangle determines the portion of the node to print in the world coordinate system. 
    * @param printRectangle a rectangle in the world coordinate system that defines the area of the contents of the 
    *      node to print. 
    */ 
    public void setPrintRectangle(final Rectangle printRectangle) { 
    this.printRectangle = printRectangle; 
    } 

    /** 
    * Determines the scale and the number of rows and columns needed to print the determined contents of the component 
    * @param pageLayout the {@link javafx.print.PageLayout} that defines the printable area of a page. 
    * @return a PrintInfo instance that encapsulates the computed values for scale, number of rows and columns. 
    */ 
    public PrintInfo getPrintInfo(final PageLayout pageLayout) { 

    double contentWidth = pageLayout.getPrintableWidth(); 
    double contentHeight = pageLayout.getPrintableHeight(); 

    double localScale = getScale() * SCREEN_TO_PRINT_DPI; 

    final Rectangle printRect = getPrintRectangle(); 
    final double width = printRect.getWidth() * localScale; 
    final double height = printRect.getHeight() * localScale; 

    // calculate how many pages we need dependent on the size of the content and the page. 
    int cCount = (int) Math.ceil((width)/contentWidth); 
    int rCount = (int) Math.ceil((height)/contentHeight); 

    return new PrintInfo(localScale, rCount, cCount); 
    } 

    /** 
    * Encapsulates information for printing with a specific {@link javafx.print.PageLayout}, 
    * i.e. the scale dependent on the screen DPI as well as the number of rows and columns for poster printing. 
    */ 
    public static class PrintInfo { 
    final double scale; 
    final int rowCount; 
    final int columnCount; 

    /** 
    * Constructs a new PrintInfo instance. 
    * @param scale The scale of the content. 
    * @param rowCount The number of rows that are needed to print the content completely with the {@link javafx.print.PageLayout}. 
    * @param columnCount The number of columns that are needed to print the content completely with the {@link javafx.print.PageLayout}. 
    */ 
    public PrintInfo(final double scale, final int rowCount, final int columnCount) { 
     this.scale = scale; 
     this.rowCount = rowCount; 
     this.columnCount = columnCount; 
    } 

    public double getScale() { 
     return scale; 
    } 

    public int getRowCount() { 
     return rowCount; 
    } 

    public int getColumnCount() { 
     return columnCount; 
    } 
    } 
} 

下面是一个使用这个类打印一些简单的节点的示例性应用:

import javafx.application.Application; 
import javafx.event.ActionEvent; 
import javafx.print.PrinterJob; 
import javafx.scene.Group; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.ToolBar; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.StackPane; 
import javafx.scene.layout.VBox; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 

public class PrintTest extends Application { 

    private NodePrinter printer = new NodePrinter(); 

    private Node nodeToPrint; 

    private Rectangle printRectangle; 

    private PrinterJob job; 

    public static void main(String[] args) { 
    launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) { 

    job = PrinterJob.createPrinterJob(); 

    BorderPane root = new BorderPane(); 

    Group pane = new Group(); 

    pane.getChildren().addAll(getNodeToPrint(), getPrintRectangle()); 

    Button printButton = new Button("Print!"); 
    printButton.setOnAction(this::print); 
    root.setTop(new ToolBar(printButton)); 
    root.setCenter(pane); 
    Scene scene = new Scene(root, 1800, 700, Color.GRAY); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
    } 

    private void print(final ActionEvent actionEvent) { 
    printer.setScale(3); 
    printer.setPrintRectangle(getPrintRectangle()); 
    boolean success = printer.print(job, true, getNodeToPrint()); 
    if (success) { 
     job.endJob(); 
    } 
    } 

    private Rectangle getPrintRectangle() { 
    if (printRectangle == null) { 
     printRectangle = new Rectangle(600, 500, null); 
     printRectangle.setStroke(Color.BLACK); 
    } 
    return printRectangle; 
    } 

    private Node getNodeToPrint() { 
    if (nodeToPrint == null) { 

     Group group = new Group(); 
     group.getChildren().addAll(
      new Rectangle(200, 100, Color.RED), 
      new Rectangle(200,100, 200, 100), 
      new Rectangle(400, 200, 200, 100), 
      new Rectangle(600, 300, 200, 100), 
      new Rectangle(800, 400, 200, 100) 
    ); 

     nodeToPrint = group; 
    } 
    return nodeToPrint; 
    } 
} 

黑边矩形描述了要在世界坐标系中打印并定义的区域(即节点内容的视口)。

test application picture

这是设置为3打印机的规模结局,印制成XPS文件:

the resulting xps file