JavaFX 8 scale node for printing without changing displayed node - javafx

In JavaFX 8, I am printing a node (e.g., ScatterChart) with printerJob.printPage(). Without scaling, the printed node is cropped. If I scale for printing, then the printed node is correctly fit to the page, but the displayed node is scaled. A simple solution would be to make a copy/clone of the node, but it appears that isn't supported. Is there a better solution than scaling the node and then removing the scaling (which causes the displayed node to briefly rescale, which is unsightly)? It would seem that printing a graph would be a basic operation for JavaFX.

You can play with this app. It creates a PNG of the chart. It then prints the chart. I didn't scale the image. The actual image is located in your source folder. You can also open it using Paint and print from there. You can also code the printer settings so that the printer dialog shows up before printing.
import java.awt.Graphics;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import static java.awt.print.Printable.NO_SUCH_PAGE;
import static java.awt.print.Printable.PAGE_EXISTS;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
/**
*
* #author blj0011
*/
public class JavaFXApplication145 extends Application
{
final static String itemA = "A";
final static String itemB = "B";
final static String itemC = "F";
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final CategoryAxis yAxis = new CategoryAxis();
final BarChart<Number, String> bc = new BarChart<Number, String>(xAxis, yAxis);
bc.setTitle("Summary");
xAxis.setLabel("Value");
xAxis.setTickLabelRotation(90);
yAxis.setLabel("Item");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(2, itemA));
series1.getData().add(new XYChart.Data(20, itemB));
series1.getData().add(new XYChart.Data(10, itemC));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(50, itemA));
series2.getData().add(new XYChart.Data(41, itemB));
series2.getData().add(new XYChart.Data(45, itemC));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(45, itemA));
series3.getData().add(new XYChart.Data(44, itemB));
series3.getData().add(new XYChart.Data(18, itemC));
Button button = new Button("Print Chart");
button.setOnAction((event)->{printImage(saveAsPng(bc));});//Create the image and print it.
VBox vbox = new VBox();
vbox.getChildren().add(bc);
StackPane stackPane = new StackPane();
stackPane.getChildren().add(button);
vbox.getChildren().add(stackPane);
Scene scene = new Scene(vbox, 800, 600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
public File saveAsPng(BarChart barChart) {
WritableImage image = barChart.snapshot(new SnapshotParameters(), null);
// TODO: probably use a file chooser here
File file = new File("chart.png");
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
} catch (IOException e) {
// TODO: handle exception here
}
return file;
}
private void printImage(File file) {
Image image = new Image(file.toURI().toString());
java.awt.image.BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(new Printable() {
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
// Get the upper left corner that it printable
int x = (int) Math.ceil(pageFormat.getImageableX());
int y = (int) Math.ceil(pageFormat.getImageableY());
if (pageIndex != 0) {
return NO_SUCH_PAGE;
}
graphics.drawImage(bufferedImage, x, y, bufferedImage.getWidth(), bufferedImage.getHeight(), null);
return PAGE_EXISTS;
}
});
try {
printJob.print();
} catch (PrinterException e1) {
e1.printStackTrace();
}
}
}

Related

Updating the Width of TextField and VBox when Full screened JavaFX

whenever I try to full screen my application, it doesn't scale. I've made multiple copies of this application trying different methods but none seem to work right.
First attempt: Application was a Parent, it would scale the background but the elements inside wouldn't scale to screen size.
As an update: here is the actual Parent that was made. The layout is the original one I wrote and has no issues when it's windowed. It has a preset WIDTH and HEIGHT but when full screened, The first example picture is what it looks like where the WIDTH of the the TextField doesn't update (since it's preset and not updating to the highest WIDTH of the screen it's running on). There are two parts to this that CAN be fixed when only one is fixed. The displayed Text has a set wrapping length of the console, though it is set by using WIDTH.
Here's what the console looks like when it's windowed:
If I could find a way to change the WIDTH, I'm thinking this can be fixed for both the TextField and the setWrappingWidth().
package application.console;
import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class Ce extends Region {
public static boolean fullscreen = false;
public static double WIDTH = 990;
// 990;
// Screen.getPrimary().getBounds().getMaxX();
public static double HEIGHT = 525;
// 525;
// Screen.getPrimary().getBounds().getMaxY();
public static Font Cinzel = (Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16));
public static VBox console = new VBox(2);
public static TextField input = new TextField();
public static ScrollPane scroll = new ScrollPane();
public static BorderPane root = new BorderPane();
public static String s;
public static Parent Window() {
root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
root.setStyle("-fx-background-color: #232323;");
scroll.setContent(console);
root.setCenter(scroll);
scroll.setStyle("-fx-background: #232323;"
+ "-fx-background-color: transparent;"
+ "-fx-border-color: #232323;"
+ "-fx-focus-color: #232323;"
);
scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
console.setStyle("-fx-background-color: #232323;"
+ "-fx-focus-color: #232323;");
console.heightProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
scroll.setVvalue((Double)newValue);
}
});
HBox hbox = new HBox();
hbox.setPrefSize(WIDTH, 16);
root.setBottom(hbox);
Text carrot = new Text(" >");
carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 26));
carrot.setFill(Color.WHITE);
input.setStyle("-fx-background-color: transparent;"
+ "-fx-text-fill: #FFFFFF;"
+ "-fx-highlight-fill: #FFFFFF;"
+ "-fx-highlight-text-fill: #232323;"
// + "-fx-border-color: #FFFFFF;"
// + "-fx-border-width: .5;"
);
input.setFont(Cinzel);
input.setMinWidth(console.getWidth());
input.setOnAction(e -> {
String s = (input.getText()).stripTrailing();
input.clear();
});
Pane pane = new Pane();
root.getChildren().add(pane);
hbox.getChildren().addAll(carrot, input);
return root;
}
This isn't the main issue as I've stated, once getting the scaled width for the TextField the process of for setWrappingWidth() for displaying the text should be the if a solution is found, here's how it goes:
#SuppressWarnings("static-access")
public void print(String s, Color c) {
Ce Ce = new Ce();
HBox text1 = new HBox();
text1.setMinWidth(Ce.WIDTH);
text1.setMaxWidth(Ce.WIDTH);
Text tCarrot = new Text(" > ");
tCarrot.setFont(Ce.Cinzel);
tCarrot.setFill(c);
Text text2 = new Text();
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline tl = new Timeline();
KeyFrame kf = new KeyFrame(
Duration.seconds(textSpeed(fastText)),
e1 -> {
if(i.get() > s.length()) {
tl.stop();
} else {
text2.setText(s.substring(0, i.get()));
i.set(i.get() + 1);
}
});
tl.getKeyFrames().add(kf);
tl.setCycleCount(Animation.INDEFINITE);
tl.play();
text2.setFill(c);
text2.setFont(Ce.Cinzel);
text2.setWrappingWidth(Ce.WIDTH - 40);
text1.getChildren().addAll(tCarrot, text2);
Ce.console.getChildren().add(text1);
Ce.console.setMargin(text1, new Insets(5, 0, 0, 3));
}
Lastly, the HEIGHT of the VBox for the displayed Text works just as intended, it's just the setting/updating the WIDTH to set it to the size of the window whether Windowed of Full screened that is the main issue here.
Try this app. It will not be exactly what you want but may provide some useful help for you if you study it, if not just ignore it, tears can keep you blind, and sometimes, that is ok.
The implementation follows the suggestions you have received in the comments on your questions which together explain what is being done and why, so I won't provide much commentary on the solution here.
Type text in the input bar, press enter and it will appear in the listview for the console log. Use the Toggle full-screen button to toggle full-screen mode on or off.
Console.java
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Console extends VBox {
private final ObservableList<String> consoleLog = FXCollections.observableArrayList();
private final ListView<String> logView = new ListView<>(consoleLog);
public Console(Stage stage) {
VBox.setVgrow(logView, Priority.ALWAYS);
HBox ribbon = createRibbon(
createFullScreenToggle(stage)
);
ribbon.setMinHeight(HBox.USE_PREF_SIZE);
getChildren().addAll(
ribbon,
logView
);
}
private ToggleButton createFullScreenToggle(Stage stage) {
ToggleButton fullScreenToggle = new ToggleButton("Toggle full screen");
fullScreenToggle.setOnAction(e ->
stage.setFullScreen(
fullScreenToggle.isSelected()
)
);
return fullScreenToggle;
}
private HBox createRibbon(ToggleButton fullscreenToggle) {
Text prompt = new Text(">");
TextField input = new TextField();
input.setOnAction(e -> {
consoleLog.add(0, input.getText());
logView.scrollTo(0);
input.clear();
});
HBox.setHgrow(input, Priority.ALWAYS);
HBox ribbon = new HBox(10,
prompt,
input,
fullscreenToggle
);
ribbon.setAlignment(Pos.BASELINE_LEFT);
return ribbon;
}
public ObservableList<String> getConsoleLog() {
return consoleLog;
}
}
ConsoleApplication.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ConsoleApplication extends Application {
#Override
public void start(Stage stage) {
Console console = new Console(stage);
console.getConsoleLog().addAll(
TEXT.lines().toList()
);
stage.setScene(
new Scene(
console
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private static final String TEXT = """
W. Shakespeare - Sonnet 148
O me, what eyes hath Love put in my head,
Which have no correspondence with true sight!
Or, if the have, where is my judgement fled,
That censures falsely what they see aright?
If that be fair whereon my false eyes dote,
What means the world to say it is not so?
If it be not, then love doth well denote
Love’s eye is not so true as all men’s ‘No.’
How can it? O, how can Love’s eye be true,
That is so vex’d with watching and with tears?
No marvel then, though I mistake my view;
The sun itself sees not till heaven clears.
O cunning Love! with tears thou keep’st me blind.
Lest eyes well-seeing thy foul faults should find.
""";
}
If you want to increase the nodes height/width according to the viewport, then this's not the best practice, because every user will have the same font size at the end. What you can do is to make the font resizable by either GUI buttons or keyboard/mouse keys.
Here is a modification on your code, that will allow users to use ctrl + mouse wheel to increase/decrease the font (like any browser or terminal):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ConsoleTest extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new GameWindow().Console(), 600, 600);
stage.setTitle("Console");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class GameWindow {
public static Console c = new Console();
public Parent Console() {
for (int i = 0; i < 100; i++) c.addText(new Text("Test" + i));
return c;
}
}
class Console extends BorderPane {
private final SimpleDoubleProperty fontSize = new SimpleDoubleProperty(20);
private final ObjectBinding<Font> fontBinding = Bindings.createObjectBinding(() -> Font.font(fontSize.get()), fontSize);
private final VBox console;
public Console() {
console = new VBox();
console.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
ScrollPane scroll = new ScrollPane(console);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
scroll.setPadding(Insets.EMPTY);
Text caret = new Text(" >");
caret.fontProperty().bind(fontBinding);
caret.setFill(Color.WHITE);
TextField input = new TextField();
input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;");
input.fontProperty().bind(fontBinding);
HBox inputBar = new HBox(2, caret, input);
inputBar.setStyle("-fx-background-color: #232323;");
inputBar.setAlignment(Pos.CENTER_LEFT);
setCenter(scroll);
setBottom(inputBar);
EventHandler<ScrollEvent> scrollEvent = e -> {
if (e.isControlDown()) {
if (e.getDeltaY() > 0) {
fontSize.set(fontSize.doubleValue() + 2);
} else {
double old;
fontSize.set((old = fontSize.doubleValue()) < 10 ? old : old - 2);
}
e.consume();
}
};
inputBar.setOnScroll(scrollEvent);
console.setOnScroll(scrollEvent);
}
public void addText(Text text) {
text.fontProperty().bind(fontBinding);
text.setFill(Color.WHITE);
console.getChildren().add(text);
}
}

Javafx: How to hide XYChart Node when binding == null

I am binding a linechart to several TextFields, which contain double values as strings, or are empty. It works fine if the Field contains a number, but unfortunatey, I get an exception when it is empty.
I could find a way to handle "emptyness" and set it to 0.0, but actually I need to hide the node completey in this case (If "X" or "Y" Field is empty)
Any ideas how to solve it?
Basic Example with only one set of Textfields:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class LineChartSample extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Demo");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("X");
yAxis.setLabel("Y");
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
AnchorPane ap = new AnchorPane();
lineChart.setTitle("Demo");
XYChart.Series series = new XYChart.Series();
AnchorPane.setTopAnchor(lineChart, 5d);
AnchorPane.setLeftAnchor(lineChart, 5d);
AnchorPane.setRightAnchor(lineChart, 5d);
TextField t1 = new TextField("33.3");
TextField t2 = new TextField("33.3");
Data d = new XYChart.Data();
d.XValueProperty().bind(Bindings.when(t1.textProperty().isEmpty())
.then(0.0) // <-- here is the problem
.otherwise(Bindings.createDoubleBinding(() -> {
return Double.parseDouble(t1.getText());
}, t1.textProperty())));
d.YValueProperty().bind(Bindings.when(t2.textProperty().isEmpty())
.then(0.0) // <-- here is the problem
.otherwise(Bindings.createDoubleBinding(() -> {
return Double.parseDouble(t2.getText());
}, t2.textProperty())));
series.getData().add(d);
AnchorPane.setBottomAnchor(t1, 50d);
AnchorPane.setLeftAnchor(t1, 5d);
AnchorPane.setBottomAnchor(t2, 50d);
AnchorPane.setRightAnchor(t2, 5d);
ap.getChildren().addAll(lineChart, t1, t2);
Scene scene = new Scene(ap, 800, 600);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I found a solution by adding
t1.textProperty().addListener((observable) -> {
if (t1.getText().isEmpty() || t2.getText().isEmpty()) {
d.getNode().setVisible(false);
} else {
d.getNode().setVisible(true);
}
});
t2.textProperty().addListener((observable) -> {
if (t1.getText().isEmpty() || t2.getText().isEmpty()) {
d.getNode().setVisible(false);
} else {
d.getNode().setVisible(true);
}
});
But if anybody knows a more elegant way, I would be quite happy.

Draw Arrows over Nodes

I want to draw arrows in a group over my grid view. The example works fine with 3x3 grid. But if I change this size to e.g. 4x4 these arrows are on the wrong place.
I colorized the source field (green) and the destination field (red) to make sure I target the right cells. The program clears the arrowGroup and draws two arrows every 3s.
import eu.lestard.grid.GridModel;
import eu.lestard.grid.GridView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import static javafx.scene.paint.Color.RED;
public class App extends Application {
private GridView<States> gridView;
private StackPane stackPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
stackPane = new StackPane();
borderPane.setCenter(stackPane);
Group arrowGroup = new Group();
GridModel<States> gridModel = new GridModel<>();
gridModel.setDefaultState(States.EMPTY);
gridModel.setNumberOfColumns(3);
gridModel.setNumberOfRows(3);
gridView = new GridView<>();
gridView.setGridModel(gridModel);
stackPane.getChildren().add(gridView);
stackPane.getChildren().add(arrowGroup);
final Scene scene = new Scene(borderPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
new Thread(() -> {
for (int i = 1; i <= 1000000; i++) {
Platform.runLater( () -> {
arrowGroup.getChildren().clear();
drawArrow(arrowGroup, new Point2D(0,0), new Point2D(2,1));
drawArrow(arrowGroup, new Point2D(1,1), new Point2D(0,2));
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
}).start();
}
// getRelativeBounds, getCenter based on https://stackoverflow.com/a/43119383/772883
private void drawArrow(Group group, Point2D from, Point2D to) {
final Line line = new Line();
System.out.println(String.format("Draw arrow from cell %s to %s", from, to));
System.out.println(String.format("group coord %s %s", group.getLayoutX(), group.getLayoutY()));
// Note: (X,Y) -> (Column, Row) => access via (Y,X)
final Pane cellPane = gridView.getCellPane(gridView.getGridModel().getCell(((int) from.getY()), (int) from.getX() ));
final Pane cellPane2 = gridView.getCellPane(gridView.getGridModel().getCell((int) to.getY() , (int) to.getX()));
cellPane.setBackground(new Background(new BackgroundFill(Color.DARKGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
cellPane2.setBackground(new Background(new BackgroundFill(RED, CornerRadii.EMPTY, Insets.EMPTY)));
Bounds n1InCommonAncestor = getRelativeBounds(cellPane, gridView);
Bounds n2InCommonAncestor = getRelativeBounds(cellPane2, gridView);
Point2D n1Center = getCenter(n1InCommonAncestor);
Point2D n2Center = getCenter(n2InCommonAncestor);
System.out.println(String.format("Draw arrow from coord %s to %s", n1Center, n2Center));
System.out.println(n1Center);
System.out.println(n2Center);
line.setStartX(n1Center.getX());
line.setStartY(n1Center.getY());
line.setEndX(n2Center.getX());
line.setEndY(n2Center.getY());
group.getChildren().add(line);
}
private Bounds getRelativeBounds(Node node, Node relativeTo) {
Bounds nodeBoundsInScene = node.localToScene(node.getBoundsInLocal());
return relativeTo.sceneToLocal(nodeBoundsInScene);
}
private Point2D getCenter(Bounds b) {
return new Point2D(b.getMinX() + b.getWidth() / 2, b.getMinY() + b.getHeight() / 2);
}
public static enum States {
EMPTY,
X,
O
}
}
(If have replaced the arrows with lines to reduce the code.)
There is a gist withe the code and a gradle buildfile:
https://gist.github.com/anonymous/c54b12ee04b7e45f2e9f58e9de1d1df0
It would be great if somebody could explain why does only work with 3x3. Is there any better option than a group?

Tooltip isn't being displayed on ScrollPane

Following the tutorial here I tried to create a Tooltip on a ScrollPane using the following code:
final ScrollPane scroll = new ScrollPane();
scroll.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
pointer = MouseInfo.getPointerInfo();
point = pointer.getLocation();
color = robot.getPixelColor((int) point.getX(), (int) point.getY());
Tooltip tooltip = new Tooltip();
tooltip.setText(" " + color);
tooltip.activatedProperty();
scroll.setTooltip(tooltip);
System.out.println("Color at: " + point.getX() + "," + point.getY() + " is: " + color);
}
});
The tooltip however refuses to show itself on the ScrollPane but the output of "Color at: ..." is being printed so I am sure that handle is being called.
EDIT : On the suggestion of jewelsea , I tried putting the eventHandler on the content ,rather than the pane, but to no effect.
If I understand what you're trying to do, you really only need to install the tooltip once, and then just modify its text as the mouse moves.
This works for me:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageTooltipTest extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Image image = new Image("http://www.publicdomainpictures.net/pictures/30000/velka/tropical-paradise.jpg");
final ImageView imageView = new ImageView();
imageView.setImage(image);
final ScrollPane scroller = new ScrollPane();
scroller.setContent(imageView);
final Tooltip tooltip = new Tooltip();
scroller.setTooltip(tooltip);
scroller.getContent().addEventHandler(MouseEvent.MOUSE_MOVED, event -> {
Image snapshot = scroller.getContent().snapshot(null, null);
int x = (int) event.getX();
int y = (int) event.getY();
PixelReader pixelReader = snapshot.getPixelReader();
Color color = pixelReader.getColor(x, y);
String text = String.format("Red: %.2f%nGreen: %.2f%nBlue: %.2f",
color.getRed(),
color.getGreen(),
color.getBlue());
tooltip.setText(text);
});
root.setCenter(scroller);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Take a snapshot part of canvas in javaFX

I need to save some part of canvas to image that is from x1 > 0 and y1 > 0 to some x2 > x1 and y2 > y1. What I understand from javaFX API, snapshot must occupy a whole area of node, like
wim = new WritableImage(((int) width), ((int) height));
bufferedImage = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
parameter = new SnapshotParameters();
parameter.setTransform(new Translate(0, 200));
and then
node.snapshot(parameter, wim);
image = SwingFXUtils.fromFXImage(wim, bufferedImage);
Graphics2D gd = (Graphics2D) image.getGraphics();
gd.translate(0,200);
ImageIO.write(image, "png", file);
Hej Mohammad,
i made a small example for you with hard coded width and height for the WritableImage, maybe this will help you. I put a ChartBar on the Stage and take a snapshot of it, when the button is clicked.
package de.professional_webworkx.blog.takesnapshoot;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
/**
*
* #author Patrick Ott <Patrick.Ott#professional-webworkx.de>
*/
public class TakeSnapShoot extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
Scene scene = new Scene(root, 1024, 768);
ObservableList<String> observableArrayList = FXCollections.observableArrayList();
observableArrayList.add("Kitchen");
observableArrayList.add("Living Room");
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
final BarChart barChart = new BarChart(xAxis, yAxis);
xAxis.setLabel("Room");
XYChart.Series series = new XYChart.Series();
series.getData().add(new XYChart.Data<String, Number>("Kitchen", 1245));
series.getData().add(new XYChart.Data<String, Number>("Living Room", 245));
series.getData().add(new XYChart.Data<String, Number>("Child 1", 3445));
barChart.getData().add(series);
root.getChildren().add(barChart);
Button snapShotBtn = new Button("Take a Snapshot");
root.getChildren().add(snapShotBtn);
snapShotBtn.setOnAction((ActionEvent t) -> {
try {
SnapshotParameters parameters = new SnapshotParameters();
WritableImage wi = new WritableImage(100, 100);
WritableImage snapshot = barChart.snapshot(new SnapshotParameters(), wi);
File output = new File("snapshot" + new Date().getTime() + ".png");
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", output);
} catch (IOException ex) {
Logger.getLogger(TakeSnapShoot.class.getName()).log(Level.SEVERE, null, ex);
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Patrick

Resources