JavaFX:跨多个页面打印节点 [英] JavaFX: printing a node across multiple pages

查看:186
本文介绍了JavaFX:跨多个页面打印节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试与JDK 8中引入的JavaFX新打印API取得联系。

I tried to get in touch with the new printing API of JavaFX that was introduced in JDK 8.

考虑以下测试程序:

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

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

my question is now: How do I configure or use the printer job to print the content of the scene that is (obviously) too big for one page across multiple pages? I have tried to set the page ranges like this

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

或者

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

或更改printPage调用之间的页面范围

or change the page range between to printPage calls like this

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中它是不可能的吗?

but nothing seems to work. Always only the left half of the content is visible on the printed document for each time I call printPage. To be clear: I don't want to scale down the node that is printed to fit onto one page, I want to keep the size of the node and print it completely over mutiple pages. This was possible in Swing. Is it not possible anymore in JavaFX?

推荐答案

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

Okay so basically I used transformations to position the node in a way I want for each page that is to be printed.

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

First, the class NodePrinter that does the actual printing:

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

这是一个示例应用程序使用此类打印一些简单节点:

Here is an example application that uses this class to print some simple nodes:

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

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

The black bordered rectangle describes the area that is to printed and is defined in the world coordinate system (that is, viewport of the node content).

这是打印机规模设置为 3的结果,打印到xps文件中:

And this is the outcome with the scale of the printer set to 3, printed into a xps file:

这篇关于JavaFX:跨多个页面打印节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆