In JavaFX, what is the difference between a Pane and a Group? I can't make out any difference.
A Group is not resizable (meaning that its size is not managed by its parent in the scene graph), and takes on the union of the bounds of its child nodes. (So, in other words, the local bounds of a Group will be the smallest rectangle containing the bounds of all the child nodes). If it is larger than the space it is allocated in its parent, it will be clipped.
By contrast, a Pane is resizable, so its size is set by its parent, which essentially determine its bounds.
Here is a quick demo. The Group is on top and the Pane below. Both contain a fixed blue square at (100,100) and a green square which is moved by pressing the left/right arrow keys. Note how at the beginning, the blue square appears in the top left corner of the group, because the local bounds of the group start at the top-leftmost point of all its child nodes (i.e. the local bounds of the group extend from (100, 100) right and down). As you move the green rectangles "off screen", the group adjusts its bounds to incorporate the changes, wherever possible, whereas the pane remains fixed.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GroupVsPaneDemo extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Group group = new Group();
VBox.setVgrow(group, Priority.NEVER);
VBox.setVgrow(pane, Priority.NEVER);
VBox vbox = new VBox(group, pane);
Rectangle rect1 = new Rectangle(100, 100, 100, 100);
Rectangle rect2 = new Rectangle(100, 100, 100, 100);
Rectangle rect3 = new Rectangle(200, 200, 100, 100);
Rectangle rect4 = new Rectangle(200, 200, 100, 100);
rect1.setFill(Color.BLUE);
rect2.setFill(Color.BLUE);
rect3.setFill(Color.GREEN);
rect4.setFill(Color.GREEN);
group.getChildren().addAll(rect1, rect3);
pane.getChildren().addAll(rect2, rect4);
Scene scene = new Scene(vbox, 800, 800);
scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
double deltaX ;
switch(e.getCode()) {
case LEFT:
deltaX = -10 ;
break ;
case RIGHT:
deltaX = 10 ;
break ;
default:
deltaX = 0 ;
}
rect3.setX(rect3.getX() + deltaX);
rect4.setX(rect4.getX() + deltaX);
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The few important difference between Pane and Group is that :
Pane can have its own size, where as a Group will take on the collective bounds of its children and is not directly resizable.
Pane can be used when you want to position its nodes at absolute position.
Also, note that Group was designed to be very lightweight and doesn't support a lot of styles. For example, you can't set border or background color for the group.
See this answer for more details.
Related
I have a javafx application and a rectangle inside it and I use rectangle.setFill() for filling the rectangle with an image. I want to know how I can reverse the image of my rectangle. ( I want to reverse it both vertically and horizontally and their combinations.)
Let's say the image I put on my rectangle is blue and has a red circle on the upper right part. I want to have images with the red circle being located in the lower right, lower left, and upper left side of the rectangle.
I have found some solutions with Canvas and GraphicsContext but it seems they are not applicable to Rectangle. Any solutions for Rectangle?
Also, I have put my rectangle in an anchorpane, if it's important to know.
The easiest option is probably to transform the node, rather than trying to flip the image itself. Two benefits of that are:
JavaFX provides easy ways to transform (translate, rotate, scale, etc.) nodes, and
You can use a single Image for all nodes.
The simplest transform to use in this case, as pointed out by #mipa, is scaling. To flip the node horizontally use node.setScaleX(-1). To flip the node vertically use node.setScaleY(-1).
Here's an example showing all four of your desired orientations:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
double width = 250;
double height = 250;
Image image = new Image(/* your image URL */, width, height, true, true);
ImagePattern fill = new ImagePattern(image);
Rectangle normal = new Rectangle(width, height, fill);
Rectangle horizontal = new Rectangle(width, height, fill);
Rectangle vertical = new Rectangle(width, height, fill);
Rectangle both = new Rectangle(width, height, fill);
flipNode(horizontal, true, false);
flipNode(vertical, false, true);
flipNode(both, true, true);
GridPane grid = new GridPane();
grid.setVgap(10);
grid.setHgap(10);
grid.setPadding(new Insets(10));
grid.setAlignment(Pos.CENTER);
grid.add(normal, 0, 0);
grid.add(horizontal, 1, 0);
grid.add(vertical, 0, 1);
grid.add(both, 1, 1);
primaryStage.setScene(new Scene(grid));
primaryStage.show();
}
private void flipNode(Node node, boolean horiztonally, boolean vertically) {
node.setScaleX(horiztonally ? -1 : 1);
node.setScaleY(vertically ? -1 : 1);
}
}
The other transform you could use is rotation, but the above is easier.
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;
public class TextSize extends javafx.application.Application {
#Override
public void start(Stage stage) {
var rectangle = new Rectangle(600, 100, Color.YELLOWGREEN);
var text = new Text(0, 50, "EXAMPLING");
text.setFont(Font.font("Monospaced", 100));
text.setBoundsType(TextBoundsType.VISUAL); // Removes text padding, but not the one on the left.
text.setTextAlignment(TextAlignment.CENTER); // Doesn't align text horizontally.
text.setTextOrigin(VPos.CENTER); // Aligns text vertically.
stage.setScene(new Scene(new Pane(rectangle, text)));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I need to edit the text.
I primarily want to set the correct Text size. Specifically, the distance between the Text and each edge of the Rectangle should be at least 5 pixels. I could achieve this by defining the size of the Text in pixels. In this case, the text height should be a maximum of 90px and the text length should be a maximum of 590px. However, the font size is not defined in pixels. Even if I enter it using fxml, it has no effect. How do I define it, please?
I want to place the text in the center of the Rectangle. Therefore, I need the position of the Text to define its center. var text = new Text(300, 50, "EXAMPLING"); But, as I said in the comments in the code, I couldn't align the text horizontally and remove the padding on the left (As can be seen from the picture). You will probably say that I should use StackPane. But, this is just an example, so I want to know if it can be achieved without it, please?
This Font has all the letters the same width, but I don't like it very much. Do you know of a better Font (Arial style), which also has the same width letters?
Please help.
Thank you
UPDATE:
I solved the size(1.) and font(3.) of the text using this code.
var rectangle = new Rectangle(600, 100, Color.YELLOWGREEN);
var text = new Text(0, rectangle.getHeight() * 0.5, "EXAMPLING");
text.setFont(Font.loadFont(getClass().getResourceAsStream("GT Pressura Mono Regular Regular.ttf"), rectangle.getHeight() * 1.3));
text.setBoundsType(TextBoundsType.VISUAL);
var max_width = rectangle.getWidth() - 7;
for (var i = text.getFont().getSize() - 0.5; text.getLayoutBounds().getWidth() >= max_width; i -= 0.5) {
text.setFont(new Font(text.getFont().getName(), i));
}
text.setTextAlignment(TextAlignment.CENTER);
text.setTextOrigin(VPos.CENTER);
stage.setScene(new Scene(new Pane(rectangle, text)));
stage.show();
However, I still don't know how to solve the positioning(2.).
I am trying to write code that will draw 3 shapes diagonally across a grid. The first two shapes are a square and a circle, which I was able to do.
The third shape, however, is giving me some grief. I am supposed to draw a cross (T version, not X), and every time I write out the code it comes out looking like a sideways, ⊢. I know I am just missing something simple, but I would really appreciate the help!
Here is the full code for my Shapes program.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.paint.Color;
public class Shapes extends Application {
public void start(Stage primaryStage) {
// This will build the shapes which include a Square, Circle, and 2 Lines.
// All shapes will have a width of 3.
// This Rectangle will be colored like the Square on a playstation controller
Rectangle square = new Rectangle(65, 65, 65, 65);
square.setStroke(Color.rgb(243, 211, 234));
square.setStrokeWidth(3);
square.setFill(Color.rgb(243, 211, 234));
// A circle colored like the Circle on the playstation controller.
Circle circle = new Circle(40);
circle.setStroke(Color.rgb(241, 188, 194));
circle.setStrokeWidth(3);
circle.setFill(Color.rgb(241, 188, 194));
// Lines colored like the Cross button on a playstation controller.
Line line1 = new Line(-50, 75, 50, 75);
line1.setStroke(Color.rgb(165, 191, 214));
line1.setStrokeWidth(3);
Line line2 = new Line(0, 0, 0, 100);
line2.setStroke(Color.rgb(165, 191, 214));
line2.setStrokeWidth(3);
// Setup the GridPane in the center of the stage which will also pad out from the edge of the window.
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
// Place each object in it's respective place on the pane.
// Square top left, Circle, middle, Cross bottom right.
pane.add(square, 0, 0);
pane.add(circle, 1, 1);
pane.add(line1, 2, 2);
pane.add(line2, 2, 2);
// Create the scene to display the program.
Scene scene = new Scene(pane);
primaryStage.setTitle("Shapes");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setResizable(false);
}
public static void main(String[] args) {
launch(args);
}
}
And here is the specific snippet I am having trouble with.
// Lines colored like the Cross button on a playstation controller.
Line line1 = new Line(-50, 75, 50, 75);
line1.setStroke(Color.rgb(165, 191, 214));
line1.setStrokeWidth(3);
Line line2 = new Line(0, 0, 0, 100);
line2.setStroke(Color.rgb(165, 191, 214));
line2.setStrokeWidth(3);
I do need the horizontal line to be a bit higher up on the pane. It should resemble a "Christian cross."
Any help is much appreciated.
It looks like the geometry is OK, but the alignment of line2 is wrong. Among the several ways to center it,
Set the alignment explicitly for the relevant GridPane child node:
pane.setHalignment(line2, HPos.CENTER);
Add the lines to a Pane having the desired layout; StackPane, for example, defaults to Pos.CENTER:
StackPane lines = new StackPane(line1, line2);
pane.add(lines, 2, 2);
As an aside, judicious use of constants will make tinkering a little easier. For example, use a scale value to keep sizes proportional, as shown here:
private static final int N = 50;
…
Rectangle square = new Rectangle(2 * N, 2 * N);
Circle circle = new Circle(N);
Line line1 = new Line(-N, 0, N, 0);
Line line2 = new Line(0, -N, 0, N);
I do need the horizontal line to be a bit higher up on the pane. It should resemble a "Christian cross."
Using the approach suggested by #fabian, adjust the horizontal line's endpoints as desired; note the changes for a Latin cross, seen in the image below:
Line line1 = new Line(-N, 0, N, 0); // Greek
Line line1 = new Line(-N, -N/3, N, -N/3); // Latin
…
pane.add(new Group(line1, line2), 2, 2);
GridPane aligns it's children inside the cells you add them to. This results in the relative position of the Lines changing. To fix this I recommend wrapping the Lines in a parent that does not reposition it's children, e.g. Group.
The following change will result in a "christian cross"-like shape rotated by 180°.
// pane.add(line1, 2, 2);
// pane.add(line2, 2, 2);
pane.add(new Group(line1, line2), 2, 2);
I am having an issue with scaling a group containing shapes, and dragging the shapes around.
In the example below, I have a group containing 2 rectangles, after scaling the group, if I drag one of the rectangles, the other will go negative the distance of the other, i.e., if I drag the rectangle r2 down, the rectangle r will go up, etc
I am wondering if this is some sort of bug, or what the issue is here?
The code below is a working example of what my issue is.
NOTE: I ran a test on the bounds, and it only changes when the dragged rectangle moves outside the current bounds.
if moving to the bottom right the new bounds will be something similar to
BoundingBox [minX:250.0, minY:250.0, minZ:0.0, width:301.7606201171875, height:338.6553955078125, depth:0.0, maxX:551.7606201171875, maxY:588.6553955078125, maxZ:0.0]
Even though the other rectangle moves outside of the minX and minY bounds.
import javafx.scene.shape.Rectangle;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SampleScale extends Application {
#Override
public void start(Stage primaryStage) {
Group g = new Group();
Rectangle r = new Rectangle(250,250,10,10);
Rectangle r2 = new Rectangle(260,260,10,10);
r2.setFill(Color.RED);
r2.setOnMouseDragged(event
->
{
r2.setTranslateX(r2.getTranslateX() + (event.getX() -r2.getX()));
r2.setTranslateY(r2.getTranslateY() + (event.getY() -r2.getY()));
g.autosize();
event.consume();
});
Pane p = new Pane();
p.setPrefSize(1000, 1000);
g.getChildren().addAll(r,r2);
p.getChildren().addAll(g);// comment out to test bottom code;
//default works
//g.setScaleX(1);
//g.setScaleY(1);
//causes other node to move negative distance of the other object
// i.e., if r2 is dragged down, r will move up, etc.
g.setScaleX(2);
g.setScaleY(2);
//-------------------------
//this works if not placed inside a group
// p.getChildren().addAll(r,r2);
// r.setScaleX(2);
// r.setScaleY(2);
// r2.setScaleX(2);
// r2.setScaleY(2);
Scene scene = new Scene(p, 1300, 1250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The scaleX and scaleY properties use the center of a node as pivot point for scaling. Since moving r2 resizes the Group, the other children also move.
You could apply a Scale transform with pivot point (0, 0) instead:
// g.setScaleX(2);
// g.setScaleY(2);
g.getTransforms().add(new Scale(2, 2, 0, 0));
I have a ListView that contains ListCells, which contain an icon defined by a SvgPath.
private class CustomCell extends ListCell<String> {
private SVGPath icon = new SVGPath();
public CustomCell() {
icon.setContent("...");
}
#Override
public void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
if(empty || value == null) {
setGraphic(null);
setText(null);
} else {
setGraphic(icon);
setText(value);
}
}
}
I want the icon to fill the height of the list cell (which is about 30px). But it´s always shown extremely large, and the size of the cell is set to the height of the icon.
Even if I set the size of the list cell and the icon
public CustomCell()
{
setPrefHeight(30);
setMaxHeight(30);
icon.setContent("...");
icon.maxHeight(30);
icon.prefHeight(30);
}
it doesn´t work. The height of the list cell is correct, but the icon still is shown too large.
Where am I wrong?
EDIT:
This is the path of my svg:
M 289.00,74.00 C 299.18,61.21 307.32,52.80 320.00,42.42 331.43,33.07 343.66,26.03 357.00,19.84 427.64,-12.98 509.92,2.91 564.42,58.28 583.93,78.10 595.94,99.15 605.58,125.00 607.76,130.86 611.37,144.75 612.54,151.00 613.15,154.23 613.28,160.06 615.58,162.44 617.49,164.42 624.11,165.84 627.00,166.86 634.80,169.62 639.97,172.04 647.00,176.42 673.69,193.07 692.76,221.39 695.83,253.00 700.60,302.03 676.64,345.41 630.00,364.00 621.17,367.52 608.48,370.99 599.00,371.00 599.00,371.00 106.00,371.00 106.00,371.00 96.50,370.99 87.00,368.97 78.00,366.00 36.29,352.22 6.21,312.25 6.00,268.00 5.77,219.90 34.76,179.34 81.00,165.02 96.78,160.14 107.02,161.00 123.00,161.00 124.59,150.68 130.49,137.79 136.05,129.00 150.70,105.88 173.22,88.99 200.00,82.65 213.13,79.55 219.79,79.85 233.00,80.00 247.37,80.17 264.61,85.94 277.00,93.00 279.11,86.37 284.67,79.45 289.00,74.00 Z
Its a cloud which I want to show in a specific size (e.g. 30, 30).
But I can´t find a way how to set a specific size.
Use a Region
After using SVGPath for sometime, I found its best to set it as a shape to a Region and define min/pref/max size on this region. This way their is no need to use the scaleX/Y factors, which leads to a difference in layout bounds and bounds in parent sizes.
To achieve this, we need to:
Define a new Region and set the SVGPath as the shape.
Define the min/pref/max size for this region.
Set a background color for this region which gets set as the fill for the svg shape.
MCVE
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
import javafx.stage.Stage;
public class SVGPathResize extends Application {
private static final double REQUIRED_WIDTH = 50.0;
private static final double REQUIRED_HEIGHT = 30.0;
#Override
public void start(Stage primaryStage) throws Exception {
SVGPath svg = new SVGPath();
svg.setContent("M 289.00,74.00 C 299.18,61.21 307.32,52.80 320.00,42.42 331.43,33.07 343.66,26.03 357.00,19.84 427.64,-12.98 509.92,2.91 564.42,58.28 583.93,78.10 595.94,99.15 605.58,125.00 607.76,130.86 611.37,144.75 612.54,151.00 613.15,154.23 613.28,160.06 615.58,162.44 617.49,164.42 624.11,165.84 627.00,166.86 634.80,169.62 639.97,172.04 647.00,176.42 673.69,193.07 692.76,221.39 695.83,253.00 700.60,302.03 676.64,345.41 630.00,364.00 621.17,367.52 608.48,370.99 599.00,371.00 599.00,371.00 106.00,371.00 106.00,371.00 96.50,370.99 87.00,368.97 78.00,366.00 36.29,352.22 6.21,312.25 6.00,268.00 5.77,219.90 34.76,179.34 81.00,165.02 96.78,160.14 107.02,161.00 123.00,161.00 124.59,150.68 130.49,137.79 136.05,129.00 150.70,105.88 173.22,88.99 200.00,82.65 213.13,79.55 219.79,79.85 233.00,80.00 247.37,80.17 264.61,85.94 277.00,93.00 279.11,86.37 284.67,79.45 289.00,74.00 Z");
final Region svgShape = new Region();
svgShape.setShape(svg);
svgShape.setMinSize(REQUIRED_WIDTH, REQUIRED_HEIGHT);
svgShape.setPrefSize(REQUIRED_WIDTH, REQUIRED_HEIGHT);
svgShape.setMaxSize(REQUIRED_WIDTH, REQUIRED_HEIGHT);
svgShape.setStyle("-fx-background-color: black;");
Scene scene = new Scene(new StackPane(svgShape), 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Use scaleX/Y on SVGPath
You can use the scaleX / scaleY to define the width and height of a Shape.
Find the original width/height of the Shape and then find the scaling factor by dividing the required width/height by the original values.
Scaling factor for width = required width / original width
Scaling factor for height = required height / original height
Now, you can use this scaling factor to scale the shape to the required size.
shape.setScaleX(Scaling factor for width);
shape.setScaleY(Scaling factor for height);
MCVE
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
import javafx.stage.Stage;
public class SVGPathResize extends Application {
private static final double REQUIRED_WIDTH = 50.0;
private static final double REQUIRED_HEIGHT = 30.0;
#Override
public void start(Stage primaryStage) throws Exception {
SVGPath svg = new SVGPath();
svg.setContent("M 289.00,74.00 C 299.18,61.21 307.32,52.80 320.00,42.42 331.43,33.07 343.66,26.03 357.00,19.84 427.64,-12.98 509.92,2.91 564.42,58.28 583.93,78.10 595.94,99.15 605.58,125.00 607.76,130.86 611.37,144.75 612.54,151.00 613.15,154.23 613.28,160.06 615.58,162.44 617.49,164.42 624.11,165.84 627.00,166.86 634.80,169.62 639.97,172.04 647.00,176.42 673.69,193.07 692.76,221.39 695.83,253.00 700.60,302.03 676.64,345.41 630.00,364.00 621.17,367.52 608.48,370.99 599.00,371.00 599.00,371.00 106.00,371.00 106.00,371.00 96.50,370.99 87.00,368.97 78.00,366.00 36.29,352.22 6.21,312.25 6.00,268.00 5.77,219.90 34.76,179.34 81.00,165.02 96.78,160.14 107.02,161.00 123.00,161.00 124.59,150.68 130.49,137.79 136.05,129.00 150.70,105.88 173.22,88.99 200.00,82.65 213.13,79.55 219.79,79.85 233.00,80.00 247.37,80.17 264.61,85.94 277.00,93.00 279.11,86.37 284.67,79.45 289.00,74.00 Z");
resize(svg, REQUIRED_WIDTH, REQUIRED_HEIGHT);
Scene scene = new Scene(new StackPane(svg), 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private void resize(SVGPath svg, double width, double height) {
double originalWidth = svg.prefWidth(-1);
double originalHeight = svg.prefHeight(originalWidth);
double scaleX = width / originalWidth;
double scaleY = height / originalHeight;
svg.setScaleX(scaleX);
svg.setScaleY(scaleY);
}
}
OUTPUT
Additional Explanation
A vector graphic actually doesn´t have a size
Well not really, the path that you define while creating a Shape actually defines the size of the vector graph.
For example, I can create the same shape using :
M 100 100 L 300 100 L 200 300 z
and
M 10 10 L 30 10 L 20 30 z
but the latter is 10 times smaller than the former and so its actual size is also 10 times smaller.
If we scale the smaller triangle by a factor of 10, it will grow and be of the same size.