How to change color of text in JavaFX Label - css

I am having trouble changing colors of text that are within the JavaFX label class.
This is the code I have so far.
package Problem2;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Problem2Code extends Application {
Slider[] slider = new Slider[4];
#Override
public void start(Stage primaryStage) throws Exception {
Text text = new Text("Show Colors");
// Bottom pane
Label[] labels = new Label[4];
String[] stringLabels = {"Red", "Green", "Blue", "Opacity"};
GridPane gridPane = new GridPane();
gridPane.setHgap(30);
gridPane.setVgap(5);
gridPane.setPadding(new Insets(25));
gridPane.setAlignment(Pos.CENTER);
for (int i = 0; i < slider.length; i++) {
slider[i] = new Slider();
slider[i].setMin(0);
if (!stringLabels[i].equals("Opacity")) {
slider[i].setMax(255);
slider[i].setValue(255);
} else {
slider[i].setMax(1);
slider[i].setValue(1);
}
labels[i] = new Label(stringLabels[i]);
slider[i].valueProperty()
.addListener((obser, old, newV) -> text.setFill(getColor()));
gridPane.add(labels[i], 0, i);
gridPane.add(slider[i], 1, i);
}
StackPane stackPane = new StackPane(text);
stackPane.setPrefSize(315, 65);
BorderPane borderPane = new BorderPane(stackPane);
borderPane.setBottom(gridPane);
primaryStage.setScene(new Scene(borderPane));
primaryStage.setTitle("Color Changer");
primaryStage.show();
}
private Color getColor() {
// r g b o
double[] rgb = new double[4];
for (int i = 0; i < rgb.length; i++) {
rgb[i] = slider[i].getValue();
}
return Color.rgb((int)rgb[0], (int)rgb[1], (int)rgb[2], rgb[3]);
}
public static void main(String[] args) {
Application.launch(args);
}}
When I build it and play with the sliders, this is what it looks like.
How can I edit the colors of text "Red", "Green", and "Blue", so the text colors matches the words like this?
I believe it has to do something with making an HBox? I tried it with that but didn't know how to do it correctly. I also tried making variables stringLabels1, stringLabels2, stringLabels3, and stringLabels4 for each of the strings, but had trouble with that in the gridPane portion. Having trouble coding either of those.
Please help, thank you.

Use setTextFill on label, below will set text color to Red:
labels[i].setTextFill(Color.color(1, 0, 0));

You could use Color#web method:
for (int i = 0; i < slider.length; i++) {
slider[i] = new Slider();
slider[i].setMin(0);
labels[i] = new Label(stringLabels[i]);
if (!stringLabels[i].equals("Opacity")) {
slider[i].setMax(255);
slider[i].setValue(255);
labels[i].setTextFill(Color.web(stringLabels[i])); //css alternative: labels[i].setStyle("-fx-text-fill: " + stringLabels[i]);
} else {
slider[i].setMax(1);
slider[i].setValue(1);
}
slider[i].valueProperty()
.addListener((obser, old, newV) -> text.setFill(getColor()));
gridPane.add(labels[i], 0, i);
gridPane.add(slider[i], 1, i);
}
Side note: you could use one listener for all four sliders:
ChangeListener<Number> listener = (obser, old, newV) -> text.setFill(getColor());
for (int i = 0; i < slider.length; i++) {
..
slider[i].valueProperty().addListener(listener);
..
}

Related

Erasing Antialiased Shapes from a JavaFX Canvas

I have inherited a simulation program to extend with new features. The original was written as an Applet using the AWT library for graphics. Before adding the new features I want to adapt the program to the desktop and use JavaFX instead of AWT.
The simulation paints hundreds or thousands of objects dozens of times per second, then erases them and repaints them at new locations, effectively animating them. I am using a Canvas object for that part of the UI. Erasing is done by repainting the object with the background color. What I am seeing though is that erasing objects is incomplete. A kind of "halo" gets left behind though.
The following program illustrates the problem. Clicking the "Draw" button causes it to draw a few hundred circles on the Canvas using the foreground color. After drawing, clicking the button again will erase the circles by re-drawing them in the background color. Multiple cycles of draw/erase will build up a visible background of "ghost" images.
package com.clartaq.antialiasingghosts;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
}
default -> Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I can get good erasure by expanding the diameter of the circles by 2 pixels when erasing. Of course, that can affect nearby shapes too.
Also, using the fillRect method to erase the entire Canvas seems reasonable, but that means everything has to be re-drawn if anything has to be re-drawn. I suppose it is possible to optimize the re-draw by erasing and re-drawing a smaller section of the Canvas but I don't want to do that if it isn't necessary.
Magnifying sections of the program display shows that it is really an antialiasing effect. Constructing the Scene with the SceneAntialiasing.DISABLED parameter does not seem to have any effect.
Attempting to turn off image smoothing as suggested in this question does not help.
Is possible to erase a single shape drawn on a Canvas by re-drawing it in the background color?
I am using Java 17.0.1, JavaFX 17.0.1, and a 5K Mac display if that is relevant.
For expedience, note the difference between fillOval and strokeOval() in the GraphicsContext. You can conditionally erase the outline in drawCircles() as a function of a suitable boolean value:
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
Try a few representative shapes, e.g. fillRect, to verify the desired result.
A better alternative, IMO, is to pursue the erase -> render strategy. Complete examples seen here and here may help you establish whether the approach is scalable to your use-case. See also this related examination of resampling artifact.
Expedient approach, as tested:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
private boolean stroke;
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
stroke = true;
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
stroke = false;
}
default ->
Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

JavaFX PieChart Legend Color change

I need to change color of circles in PieChart Legend. I don't know how to get to this property of PieChart. For example I'm able to change color of text in label Legend and I think this is close to the solution.
It shows what I want to change:
#FXML
public PieChart chart;
public ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();
public void chartLoad() {
pieChartData.clear();
List<String> colorList = new ArrayList<>();
for(int i = 0; i < categoryList.getSize(); i++) {
if(categoryList.getByIndex(i).getValue() > 0) {
PieChart.Data data = new PieChart.Data(categoryList.getByIndex(i).getName(),
categoryList.getByIndex(i).getValue());
pieChartData.add(data);
data.getNode().setStyle("-fx-pie-color: " +
categoryList.getByIndex(i).getColor().getName());
colorList.add(categoryList.getByIndex(i).getColor().getName());
}
}
Set<Node> items = chart.lookupAll("Label.chart-legend-item");
int i = 0;
for(Node item : items) {
Label label = (Label) item;
label.setText("sampleText");
label.setStyle("-fx-text-fill: " + colorList.get(i));
System.out.println(label.getChildrenUnmodifiable().toString());
i++;
}
chart.setData(pieChartData);
}
Thank you for your future comments and answers.
Dynamically allocating colors to charts is a bit of a pain. If you have a fixed set of colors, without a predefined mapping from your data to the colors, you can just use an external style sheet, but doing anything else needs (as far as I know) a bit of a hack.
The default modena.css style sheet defines eight constant colors, CHART_COLOR_1 to CHART_COLOR_8. Nodes in a pie chart, including both the "pie slices" and the color swatches in the legend, are assigned a style class from the eight classes default-color0 to default-color7. Each of these style classes by default has -fx-pie-color set to one of the constants. Unfortunately, if the data in the pie chart are changed, these mappings from default-colorx to CHART_COLOR_y change in a way that isn't documented.
So the best approach for your scenario that I can find is:
add the new data to the chart
once all the data are added, for each datum look up the style class that was added to the datum's node
look up all the nodes in the chart that have that style class (this will also give the legend swatches)
update the -fx-pie-color for those nodes to the desired color
The last trap here is that you need to make sure the legend is added to the chart, and that CSS is applied to the chart, so that the lookup works.
public void chartLoad() {
pieChartData.clear();
List<String> colors = new ArrayList<>();
for(int i = 0; i < categoryList.getSize(); i++) {
if(categoryList.getByIndex(i).getValue() > 0) {
PieChart.Data data = new PieChart.Data(categoryList.getByIndex(i).getName(),
categoryList.getByIndex(i).getValue());
pieChartData.add(data);
colors.add(categoryList.getByIndex(i).getColor().getName());
}
}
chart.setData(pieChartData);
chart.requestLayout();
chart.applyCSS();
for (int i = 0 ; i < pieChartData.size() ; i++) {
PieChart.Data d = pieChartData.get(i);
String colorClass = "" ;
for (String cls : d.getNode().getStyleClass()) {
if (cls.startsWith("default-color")) {
colorClass = cls ;
break ;
}
}
for (Node n : chart.lookupAll("."+colorClass)) {
n.setStyle("-fx-pie-color: "+colors.get(i));
}
}
}
Here's a quick, complete demo of this approach:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class PieChartTest extends Application {
private final Random rng = new Random();
#Override
public void start(Stage primaryStage) throws Exception {
PieChart chart = new PieChart();
Button button = new Button("Generate Data");
button.setOnAction(e -> updateChart(chart));
BorderPane root = new BorderPane(chart);
HBox controls = new HBox(button);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
root.setTop(controls);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateChart(PieChart chart) {
chart.getData().clear();
int numValues = 4 + rng.nextInt(10);
List<String> colors = new ArrayList<>();
List<PieChart.Data> data = new ArrayList<>();
for (int i = 0 ; i < numValues ; i++) {
colors.add(getRandomColor());
PieChart.Data d = new PieChart.Data("Item "+i, rng.nextDouble() * 100);
data.add( d );
chart.getData().add(d) ;
}
chart.requestLayout();
chart.applyCss();
for (int i = 0 ; i < data.size() ; i++) {
String colorClass = "" ;
for (String cls : data.get(i).getNode().getStyleClass()) {
if (cls.startsWith("default-color")) {
colorClass = cls ;
break ;
}
}
for (Node n : chart.lookupAll("."+colorClass)) {
n.setStyle("-fx-pie-color: "+colors.get(i));
}
}
}
private String getRandomColor() {
Color color = Color.hsb(rng.nextDouble() * 360, 1, 1);
int r = (int) (255 * color.getRed()) ;
int g = (int) (255 * color.getGreen());
int b = (int) (255 * color.getBlue()) ;
return String.format("#%02x%02x%02x", r, g, b) ;
}
public static void main(String[] args) {
Application.launch(args);
}
}
This really is a bit of a hack, so better solutions are obviously welcome.

Add FreeHand functionality in JavaFX

Actually I am making an application which allows user to crop an image an then gives functionality of free-hand drawing and to also to draw different shapes. I have achieved functionality of cropping and drawing shapes like line etc. But I am facing some problems in free-hand drawing.
I have added my image on "HBox" and cropped that image through "Rectangle" in JavaFX class which is added on "Group". And all these are added on "Pane" class. Now for free-hand drawing, I am using "Canvas". Where to add canvas, whether on "HBox" or "Group" or "Pane" class. And Canvas is only initialized on clicking pencil button. I have added single functions for Mouse-Events and applied if-checks for different functionalities in those functions.
basically how to draw pencil on image or how to add lineTo on group.
Can someone please help me in solving my problem??
`
package application;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class Main extends Application {
int dragStatus = 0;
double startingPointX;
double startingPointY;
double currentEndingPointX;
double currentEndingPointY;
BufferedImage bufferedImage;
Robot robot;
ImageView imageView;
Button pencilBtn;
Image image;
Pane rootPane;
HBox pictureRegion;
Scene scene;
Rectangle croppedArea;
Canvas canvas;
GraphicsContext gc;
WritableImage writableImage;
Group group = new Group();
Line line;
LineTo lineTo;
String shape;
Boolean canvasAdded;
#Override
public void start(Stage primaryStage) {
try {
int sleepTime = 120;
Thread.sleep(sleepTime);
pencilBtn = createButton("save.png");
pictureRegion = new HBox();
rootPane = new Pane();
scene = new Scene(rootPane);
robot = new Robot();
java.awt.Rectangle capture = new java.awt.Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
bufferedImage = robot.createScreenCapture(capture);
image = ConvertBufferedToJavaFXImage.convertToFxImage(bufferedImage);
pencilBtn.setOnAction(e -> geometrySelection("pencil"));
croppedArea = new Rectangle();
if(canvas != null) {
canvas.setOnMousePressed(e -> onMousePressed(e));
canvas.setOnMousePressed(e -> onMouseDragged(e));
}
imageView = new ImageView(image);
scene.setOnMouseEntered(e -> onMouseEntered(e, "scene"));
scene.setOnMousePressed(e -> onMousePressed(e));
scene.setOnMouseReleased(e -> onMouseReleased(e));
scene.setOnMouseDragged(e -> onMouseDragged(e));
pictureRegion.getChildren().add(imageView);
rootPane.getChildren().add(pictureRegion);
rootPane.getChildren().add(group);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setMaximized(true);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public void onMouseEntered(MouseEvent ev, String nodeType) {
if (nodeType == "scene") {
scene.setCursor(Cursor.CROSSHAIR);
} else if (nodeType == "croppedArea") {
croppedArea.setCursor(Cursor.TEXT);
//croppedArea.setOnMousePressed(e -> onMousePressed(e));
} else if (nodeType == "btn") {
pencilBtn.setCursor(Cursor.HAND);
}
}
public void onMousePressed(MouseEvent event) {
if ((startingPointX < event.getX() && event.getX() < currentEndingPointX)
&& (startingPointY < event.getY() && event.getY() < currentEndingPointY)) {
if(shape == "pencil") {
lineTo = new LineTo();
System.out.println("lineTo1");
gc.beginPath();
gc.lineTo(event.getX(), event.getY());
gc.stroke();
}
} else {
shape = "croppedArea";
rootPane.getChildren().remove(canvas);
group.getChildren().clear();
startingPointX = event.getX();
startingPointY = event.getY();
group.getChildren().add(croppedArea);
}
}
public void onMouseDragged(MouseEvent event) {
dragStatus = 1;
if (dragStatus == 1) {
// if((startingPointX < event.getX() && event.getX() < currentEndingPointX)
// && (startingPointY < event.getY() && event.getY() < currentEndingPointY)) {
if(shape == "pencil") {
System.out.println("lineTo1");
gc.lineTo(event.getX(), event.getY());
gc.stroke();
}
else {
currentEndingPointX = event.getX();
currentEndingPointY = event.getY();
rootPane.getChildren().remove(pencilBtn);
croppedArea.setFill(Color.TRANSPARENT);
croppedArea.setStroke(Color.BLACK);
croppedArea.setOnMouseEntered(e -> onMouseEntered(e, "croppedArea"));
adjustRectangleProperties(startingPointX, startingPointY, currentEndingPointX, currentEndingPointY,
croppedArea);
}
}
}
public void geometrySelection(String tempShape) {
if(tempShape == "pencil") {
shape = "pencil";
canvas = new Canvas();
gc = canvas.getGraphicsContext2D();
//gc.setFill(Color.LIGHTGRAY);
gc.setStroke(Color.BLACK);
gc.setLineWidth(5);
canvas.setLayoutX(startingPointX);
canvas.setLayoutY(startingPointY);
canvas.setWidth(croppedArea.getWidth());
canvas.setHeight(croppedArea.getHeight());
System.out.println("canvasAdded");
group.getChildren().add(canvas);
}
}
public void onMouseReleased(MouseEvent event) {
if(dragStatus == 1) {
if(shape == "croppedArea") {
if (croppedArea.getHeight() > 0 && croppedArea.getWidth() > 0) {
pencilBtn.setLayoutX(Math.max(startingPointX, currentEndingPointX) + 5);
pencilBtn.setLayoutY(Math.max(startingPointY, currentEndingPointY) - 120);
rootPane.getChildren().add(pencilBtn);
dragStatus = 0;
}
}
bufferedImage = robot.createScreenCapture(new java.awt.Rectangle((int) startingPointX, (int) startingPointY,
(int) croppedArea.getWidth(), (int) croppedArea.getHeight()));
}
}
void adjustRectangleProperties(Double startingPointX, Double startingPointY, Double currentEndingPointX,
Double currentEndingPointY, Rectangle givenRectangle) {
givenRectangle.setX(startingPointX);
givenRectangle.setY(startingPointY);
givenRectangle.setWidth(currentEndingPointX - startingPointX);
givenRectangle.setHeight(currentEndingPointY - startingPointY);
if (givenRectangle.getWidth() < 0) {
givenRectangle.setWidth(-givenRectangle.getWidth());
givenRectangle.setX(givenRectangle.getX() - givenRectangle.getWidth());
}
if (givenRectangle.getHeight() < 0) {
givenRectangle.setHeight(-givenRectangle.getHeight());
givenRectangle.setY(givenRectangle.getY() - givenRectangle.getHeight());
}
}
public Button createButton(String imageName) throws FileNotFoundException {
FileInputStream iconFile = new FileInputStream(imageName);
Image iconImage = new Image(iconFile);
Button button = new Button();
button.setGraphic(new ImageView(iconImage));
button.setMaxSize(20, 20);
button.setPadding(Insets.EMPTY);
return button;
}
public static void main(String[] args) {
launch(args);
}
}
`

How do I add a Tooltip to a rectangular region of a JavaFX Canvas

In my JavaFX app I have a TableView with multiple columns, one of which displays data in a graphical form. To do this I have created a CanvasCell object that creates and manages its own Canvas to deal with the drawing. The drawing part works just fine.
I'd now like to put Tooltips over some regions within the Canvas/Cell. There may be multiple Tooltips per Cell (which prevents me from adding the Tooltip at the Cell level) and they should only trigger in specific regions of the graph. However, I'm not managing to get it functioning at all. I don't seem to understand the interactions of Display Node hierarchy well enough (read "at all") to be able to place the Tooltip anywhere where it will actually work.
Documentation for JavaFX is sparse and Google + SO has come up blank for all searches that I've tried. Is there anyone who knows how to do this sort of thing or should I just write it off as "not an option" for now.
For info, the CanvasCell calls a draw() function inside an extended Canvas object on updateItem(). The code in which I've tried to create a Tooltip sits inside that draw() function and looks like:
Rectangle rect = new Rectangle(leftVal, topVal, width, height);
gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
Tooltip tooltip = new Tooltip("Tooltip Text");
Tooltip.install(rect, tooltip);
but that code was written more in hope than anything else and doesn't generate anything useful in the interface.
If someone can point me in the right direction, I will be very grateful.
If you don't need the timing control illustrated here, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains to condition the text as shown below.
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
As suggested here, Java 9 and later provide control over Tooltip timing via the properties showDelay and showDuration.
A similar approach is illustrated here for Swing.
import javafx.application.Application;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
/**
* #see https://stackoverflow.com/a/53785468/230513
* #see https://stackoverflow.com/a/53753537/230513
*/
public class CanvasTooltipDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 400, 400);
stage.setScene(sc);
Canvas canvas = new Canvas(200, 200);
root.getChildren().add(canvas);
Map<Color, Rectangle> tooltips = new HashMap<>();
tooltips.put(Color.RED, new Rectangle(0, 0, 100, 100));
tooltips.put(Color.BLUE, new Rectangle(100, 0, 100, 100));
tooltips.put(Color.YELLOW, new Rectangle(0, 100, 100, 100));
tooltips.put(Color.GREEN, new Rectangle(100, 100, 100, 100));
GraphicsContext gc = canvas.getGraphicsContext2D();
tooltips.forEach((color, bounds) -> {
gc.setFill(color);
gc.fillRect(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
});
setToolTips(canvas, tooltips);
stage.show();
}
private void setToolTips(Node node, Map<Color, Rectangle> tooltips) {
Tooltip tooltip = new Tooltip();
Tooltip.install(node, tooltip);
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
node.setOnMouseExited(e -> {
tooltip.hide();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
I have the same solution as per #Slaw suggested. My idea is to make it more centralized so that you can pass your node and its regions you want to show the tooltips.
In the below demo, you can use the setToolTips() as static utitlity method for multiple nodes.
Note: some part of the logic is referred from Tooltip core implementation ;)
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.Map;
public class MultiTooltipDemo extends Application {
private double lastMouseX;
private double lastMouseY;
private static int TOOLTIP_XOFFSET = 10;
private static int TOOLTIP_YOFFSET = 7;
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
StackPane box1 = new StackPane();
box1.setMaxSize(200, 200);
box1.setStyle("-fx-background-color:red, blue, yellow, green; -fx-background-insets: 0 100 100 0, 0 0 100 100, 100 100 0 0, 100 0 0 100;");
root.getChildren().add(box1);
Map<String, Rectangle2D> tooltips = new HashMap<>();
tooltips.put("I am red", new Rectangle2D(0, 0, 100, 100));
tooltips.put("I am blue", new Rectangle2D(100, 0, 100, 100));
tooltips.put("I am yellow", new Rectangle2D(0, 100, 100, 100));
tooltips.put("I am green", new Rectangle2D(100, 100, 100, 100));
setToolTips(box1, tooltips);
}
private void setToolTips(Node node, Map<String, Rectangle2D> tooltips) {
Duration openDelay = Duration.millis(1000);
Duration hideDelay = Duration.millis(5000);
Tooltip toolTip = new Tooltip();
Timeline hideTimer = new Timeline();
hideTimer.getKeyFrames().add(new KeyFrame(hideDelay));
hideTimer.setOnFinished(event -> {
toolTip.hide();
});
Timeline activationTimer = new Timeline();
activationTimer.getKeyFrames().add(new KeyFrame(openDelay));
activationTimer.setOnFinished(event -> {
Bounds nodeScreenBounds = node.localToScreen(node.getBoundsInLocal());
double nMx = nodeScreenBounds.getMinX();
double nMy = nodeScreenBounds.getMinY();
toolTip.setText("");
tooltips.forEach((str, bounds) -> {
double mnX = nMx + bounds.getMinX();
double mnY = nMy + bounds.getMinY();
double mxX = mnX + bounds.getWidth();
double mxY = mnY + bounds.getHeight();
if (lastMouseX >= mnX && lastMouseX <= mxX && lastMouseY >= mnY && lastMouseY <= mxY) {
toolTip.setText(str);
}
});
if (!toolTip.getText().isEmpty()) {
toolTip.show(node.getScene().getWindow(), lastMouseX + TOOLTIP_XOFFSET, lastMouseY + TOOLTIP_YOFFSET);
hideTimer.playFromStart();
}
});
node.setOnMouseMoved(e -> {
double buffPx = 2;
double eX = e.getScreenX();
double eY = e.getScreenY();
// Not hiding for slight mouse movements while tooltip is showing
if (hideTimer.getStatus() == Animation.Status.RUNNING) {
if (lastMouseX - buffPx <= eX && lastMouseX + buffPx >= eX && lastMouseY - buffPx <= eY && lastMouseY + buffPx >= eY) {
return;
}
}
lastMouseX = e.getScreenX();
lastMouseY = e.getScreenY();
toolTip.hide();
hideTimer.stop();
activationTimer.playFromStart();
});
node.setOnMouseExited(e -> {
toolTip.hide();
activationTimer.stop();
hideTimer.stop();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}

how to keep space between images in scrollpane

I need to space up between images in a scrollpane.I'm adding an imageview and a button to a Vbox.Then that vbox to a gridpane.gridpane is added to scrollpane.However,scrollpane gets overcrowded.I've used gridpane.setPadding(), but no effect.
here's my code:
File file = new File("D:\\SERVER\\Server Content\\Apps\\icons");
File[] filelist1 = file.listFiles();
ArrayList<File> filelist2 = new ArrayList<>();
for (File file1 : filelist1) {
filelist2.add(file1);
}
btnar=new ArrayList<>();
for (int i = 0; i < filelist2.size(); i++) {
downloadbtn=new Button("Download");
btnar.add(downloadbtn);
}
System.out.println(filelist2.size());
gridpane.setAlignment(Pos.CENTER);
gridpane.setPadding(new Insets(50, 50, 50, 50));
gridpane.setHgap(50);
gridpane.setVgap(50);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setFillWidth(true);
columnConstraints.setHgrow(Priority.ALWAYS);
gridpane.getColumnConstraints().add(columnConstraints);
int imageCol = 0;
int imageRow = 0;
for (int i = 0; i < filelist2.size(); i++) {
System.out.println(filelist2.get(i).getName());
image = new Image(filelist2.get(i).toURI().toString());
pic = new ImageView();
pic.setFitWidth(130);
pic.setFitHeight(130);
pic.setImage(image);
vb = new VBox();
vb.getChildren().addAll(pic,btnar.get(i));
gridpane.add(vb, imageCol, imageRow);
GridPane.setMargin(pic, new Insets(2,2,2,2));
imageCol++;
// To check if all the 3 images of a row are completed
if (imageCol > 2) {
// Reset Column
imageCol = 0;
// Next Row
imageRow++;
}
}
here' how my stage looks like when images are added
Your code looks fine to me. I have tried your code with a folder filled with images. I wrapped the GridPane on a ScrollPane and gave the ScrollPane a defined size. The space between ImageViews are as per the HGap and VGap provided. Here is a image to second my findings.
Note
If you are looking for gaps between ImageView in a GridPane use setHgap() and setVgap()
setPadding() is used to set the margin around the grid. You can ofcourse use a mix of both as I have done below
Please find this MCVE to support you find your mistake
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class LoadingImages extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ScrollPane scrollPane = new ScrollPane();
GridPane gridpane = new GridPane();
scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scrollPane.setPrefSize(500, 500);
scrollPane.setContent(gridpane);
Scene scene = new Scene(scrollPane);
primaryStage.setScene(scene);
primaryStage.show();
File file = new File("Path to folder with images");
File[] filelist1 = file.listFiles();
ArrayList<File> filelist2 = new ArrayList<>();
for (File file1 : filelist1) {
filelist2.add(file1);
}
List btnar=new ArrayList<>();
for (int i = 0; i < filelist2.size(); i++) {
Button downloadbtn=new Button("Download");
btnar.add(downloadbtn);
}
System.out.println(filelist2.size());
gridpane.setAlignment(Pos.CENTER);
gridpane.setPadding(new Insets(50, 50, 50, 50));
gridpane.setHgap(50);
gridpane.setVgap(50);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setFillWidth(true);
columnConstraints.setHgrow(Priority.ALWAYS);
gridpane.getColumnConstraints().add(columnConstraints);
int imageCol = 0;
int imageRow = 0;
for (int i = 0; i < filelist2.size(); i++) {
System.out.println(filelist2.get(i).getName());
Image image = new Image(filelist2.get(i).toURI().toString());
ImageView pic = new ImageView();
pic.setFitWidth(130);
pic.setFitHeight(130);
pic.setImage(image);
VBox vb = new VBox();
vb.getChildren().addAll(pic,(Button)btnar.get(i));
gridpane.add(vb, imageCol, imageRow);
GridPane.setMargin(pic, new Insets(2,2,2,2));
imageCol++;
// To check if all the 3 images of a row are completed
if (imageCol > 2) {
// Reset Column
imageCol = 0;
// Next Row
imageRow++;
}
}
}
public static void main(String[] args) {
launch(args);
}
}

Resources