Here is my summary code
public class KlArgon extends Application {
BorderPane border ;
Scene scene ;
Stage stage;
#Override
public void start(Stage stage) {
:
border = new BorderPane();
:
HBox infoBox = addInfoHBox();
border.setTop(infoBox);
:
VBox menuBox = addMenuVBox();
border.setLeft(menuBox);
:
border.setCenter(addAnchorPane(addGridPane()));
// setRight and setBottom is not used
:
scene = new Scene (border);
stage.setScene(scene);
stage.show();
}
private Node addAnchorPane(GridPane grid) {
AnchorPane anchorpane = new AnchorPane();
anchorpane.getChildren().add(grid);
AnchorPane.setTopAnchor(grid, 10.0);
return anchorpane;
}
private GridPane addGridPane() {
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(0, 10, 0, 10));
grid.add(addWhiteboard(), 1, 0);
return grid;
}
private Node addWhiteboard() {
Canvas canvas = new Canvas (wboardWd, wdboardHt);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawShapes(gc);
drawfromClipboard(gc);
return canvas;
}
}
I refer to the Center pane as the "Whiteboard". Among other things, I have two buttons in menuBox - btnCopyFromClipboard and btnClearWhiteboard.
When user presses btnCopyFromClipboard - the user should be able to draw an rectangle in the "Whiteboard" only (i.e. Center pane only) and then the clipboard image will be copied (scaled) into that rectangle.
So I made border,scene, stage as global and I am trying to get this to work - not only it is buggy/ugly- to me it looks like a hack. Is there a cleaner way to do this i.e. manage Center Pane when button in left pane is pressed?
Basically I want the Center Pane to be the Canvas and the GraphicsContext operations are performed whe the Buttons in Left Pane is pressed.
What I have working is pressing the btnCopyFromClipboard lets me draw the rectangle anywhere/everywhere (instead of limiting it to the Center Pane / the whiteboard). I want to restrict the rectangle to be drawn inside the Center Pane / the whiteboard only.
Some inputs/pointers from someone who has been through this will be very much appreciated.
http://docs.oracle.com/javafx/2/layout/builtin_layouts.htm was helpful to get me started.
I was in a fix as well and here is a question which I asked in Oracle Forums, to which, James gave me a vivid reply. Please go through this, it has your answer
https://community.oracle.com/thread/2598756
Related
So I have to make a Zodiac Sign GUI, and we are tasked with having the following:
a Label in the top left, and a TextField in the top right (both with padding)
an exit Button in the center of the GUI, along with a clear and find my sign on either side
and finally, a Label in the bottom center prompting the sign
I am utterly confused on how to have this come out, as I am a novice in JavaFX. I believe I would need a branch node along with the root node in order to get this kind of layout. I do not need assistance in instantiating the button, labels etc., mainly confused with how this layout can even work. The code I have now is the following:
public class ZodiacGUI extends Application {
public static void main(String args[]) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane mainPane = new BorderPane();
mainPane.setStyle("-fx-background-color: PINK");
setupControls(mainPane);
Scene scene = new Scene(mainPane);
setStage(primaryStage, scene);
}
public void setStage(Stage primaryStage, Scene scene) {
primaryStage.setWidth(500);
primaryStage.setHeight(200);
primaryStage.setTitle("What is my Zodiac Sign?");
primaryStage.setScene(scene);
primaryStage.show();
}
public void setupControls(BorderPane mainPane) {
Label label = new Label("Enter you birthday formatted as -> mm/dd");
Button exitButton = new Button();
Button findSign = new Button();
Button clear = new Button();
TextField userInput = new TextField();
userInput.setPromptText("Enter birthday");
exitButton.setText("Exit.");
findSign.setText("Find my sign.");
clear.setText("Clear.");
exitButton.setOnAction(e -> System.exit(0));
mainPane.setLeft(label);
mainPane.setRight(userInput);
mainPane.setCenter(exitButton);
mainPane.setCenter(findSign);
mainPane.setCenter(clear);
BorderPane.setAlignment(label, Pos.TOP_LEFT);
BorderPane.setAlignment(userInput, Pos.TOP_RIGHT);
BorderPane.setAlignment(exitButton, Pos.CENTER);
BorderPane.setAlignment(findSign, Pos.CENTER_LEFT);
BorderPane.setAlignment(clear, Pos.CENTER_RIGHT);
}
}
This only outputs one of the buttons out of the three, as I assume it is because the necessary addition of another BorderPane? Here is a drawn out picture of what I would like to come out with:
Just to clarify, I do not need assistance with the handling of finding the zodiac sign, etc. Mainly need assistance on the layout, as it has stumped me for days. Thank you in advance for helping out a novice to JavaFX :).
You have three rows with diffrent number of children. You can use HBox if row have more than one child.
BorderPane mainPane = new BorderPane();
mainPane.setTop(new HBox(topLabel, topField));
mainPane.setCenter(new HBox(centerLabel, centerField, centerButtom));
mainPane.setBottom(bottomCenterButton);
If you need more than 3 rows (top, center, bottom section of BorderPane) you can use VBox, where every child is row like:
HBox row1 new HBox(child1, child2)
HBox row2 new HBox(child1, child2, child3)
HBox row3 new HBox(child1)
HBox row4 new HBox(child1, child2)
VBox pane = new VBox(row1, row2, row3, row4);
You might want to use a GridPane
GridPane grid = new GridPane();
grid.add(label,0,0);
grid.add(userInput,2,0);
grid.add(findSign,0,1);
grid.add(exitButton,1,1);
grid.add(clear,2,1);
or use a VBox with BorderPane to help with the layout/alignment
BorderPane mainPane, centerPane;
mainPane.setLeft(label);
mainPane.setRight(userInput);
centerPane.setLeft(findSign);
centerPane.setRight(clear);
centerPane.setCenter(exitButton);
BorderPane.setAlignment(label, Pos.LEFT);
BorderPane.setAlignment(userInput, Pos.RIGHT);
BorderPane.setAlignment(exitButton, Pos.CENTER);
BorderPane.setAlignment(findSign, Pos.LEFT);
BorderPane.setAlignment(clear, Pos.RIGHT);
VBox items = new VBox();
items.getChildren().addAll(mainPane, centerPane);
I have a Pane and a VBox inside of a StackPane. I added the Pane first and the VBox on top of it. The VBox includes several kes that in turn have Buttons as children. I use the normal Pane as a "canvas" to position Lines on it. The Lines as well as the Buttons need to be interactive. So by clicking on them they shall for example change their color.
But at the moment the Pane and its Line objects are shown but they are covered by the VBox so I can not interact with them but only with the Buttons.
How can I provide that I can interact with the Line as well, though they are in a lower layer of the StackPane?
You can set the pickOnBoundsProperty of your container Nodes (VBox and HBox) to false.
Defines how the picking computation is done for this node when
triggered by a MouseEvent or a contains function call. If pickOnBounds
is true, then picking is computed by intersecting with the bounds of
this node, else picking is computed by intersecting with the geometric
shape of this node.
As result, "transparent" areas of the HBox and the VBox will not register the click event.
Example:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
Pane pane = new Pane();
pane.setStyle("-fx-background-color:red;");
StackPane sp = new StackPane();
VBox vbox = new VBox();
HBox hbox = new HBox();
hbox.setSpacing(30);
for (int i = 0; i< 5; i++) {
Button b = new Button("Button");
b.setOnAction(e -> System.out.println("Button clicked"));
hbox.getChildren().add(b);
}
vbox.getChildren().add(hbox);
sp.getChildren().addAll(pane, vbox);
Line line = new Line(10, 10, 500, 10);
line.setStrokeWidth(3);
pane.getChildren().add(line);
line.setOnMouseClicked(e -> {
System.out.println("Line Clicked!");
});
// Set pickOnBounds to vbox and hbox
vbox.setPickOnBounds(false);
hbox.setPickOnBounds(false);
root.setCenter(sp);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
They are covered cause the VBOX is in front of the Pane.
First Way:
You can setVisible(false) the VBox so the Pane can be accessible and then setVisible(true) the VBox again.
Second Way:
You can use methods called toBack(); and toFront(); and bring a Node back or front to the hierarchy:
vBox.toBack(); //the the vBox goes back to the hierarchy,it is like zOrder in html
and then use:
vBox.toFront(); //to bring the vBox again in front.
Finally:
You can somewhere provide a ToggleButton that when is pressed the VBox is appearing and when is not pressed the VBox is disappeared.
I am a beginner who just recently started learning JavaFX, and I seem to be making the same reoccurring mistake within my programs. For example, in the following code I am trying to draw the X-Axis and Y-Axis and have it binded to half the width and height of the pane. When executed, the axes are very small and located at the topleft corner, but as you resize the window of the application, the axes slowly increase in size until the window not being resized anymore.
public class Debug extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class GraphFunc extends Pane {
private double xAxisSpan, yAxisSpan;
public GraphFunc() {
xAxisSpan = 100;
yAxisSpan = 100;
drawAxes();
}
private void drawAxes() {
Line xAxis = new Line(0, 50, 100, 50);
Line yAxis = new Line(50, 0, 50, 100);
xAxis.setStartX(0);
xAxis.startYProperty().bind(heightProperty().divide(2));
xAxis.endXProperty().bind(widthProperty());
xAxis.endYProperty().bind(heightProperty().divide(2));
yAxis.startXProperty().bind(widthProperty().divide(2));
yAxis.setStartY(0);
yAxis.endXProperty().bind(widthProperty().divide(2));
yAxis.endYProperty().bind(heightProperty());
getChildren().addAll(xAxis, yAxis);
}
}
I am confused because when pane is change to a StackPane, this does not happen. Also if I moved the code in drawAxes() to start() and added the lines to pane it would also not do this. Please explain, I cannot seem to understand what is happening after researching and playing around with it.
This is happening because of Pane. Pane is meant to be used when absolute positioning of children is required.
This is from Oracle documentation:
This class may be used directly in cases where absolute positioning of children is required since it does not perform layout beyond resizing resizable children to their preferred sizes. It is the application's responsibility to position the children since the pane leaves the positions alone during layout.
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
If you remove the width and height from the scene and instead set the width and height directly on to test like this:
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
GraphFunc test = new GraphFunc();
test.setPrefSize(500, 500);
pane.getChildren().add(test);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
You will get this:
drawn image
However you will not be able to span.
From Oracle documentation:
Note: if an application needs children to be kept aligned within a
parent (centered, positioned at top-left, etc), it should use a
StackPane instead.
Also from Oracle documentation:
Pane does not clip its content by default, so it is possible that
childrens' bounds may extend outside its own bounds, either if
children are positioned at negative coordinates or the pane is resized
smaller than its preferred size.
I have this code (simplified/shortend/pseudo):
StackPane background = new StackPane();
GridPane overlay = new GridPane();
BorderPane pane = new BorderPane(background);
pane.setLeft(overlay);
I want the background/Stack pane to cover the entire window (in the background, surprise), and the overlay/Grid to cover a part of the background. The issue is that when I add an ImageView to the StackPane, the image shows up and nothing else. The overlay is not visible. I have tried overlay.toFront, overlay.preferredHeight, and background.toBack, with no success.
Try putting the border pane into the stack pane, instead of the other way around, and then making sure you add the image as the first element of the stack pane's children.
GridPane overlay = new GridPane();
BorderPane pane = new BorderPane();
pane.setLeft(overlay);
StackPane background = new StackPane(pane);
// ...
ImageView imageView = ... ;
background.getChildren().add(0, imageView);
// ...
scene.setRoot(background);
Alternatively, just use a BorderPane as the root and add the nodes in the usual way. Then, in an external style sheet, do
.root {
-fx-background-image: url(relative/path/to/image);
}
I am working on an application that enables the user to draw graphs, i.e. edges and nodes. As nodes I am currently using plain JavaFX label elements. When drawing the edges, I need to consider the bounds of the label, however, the width and the height of the labels seem to be initialized only after "drawing" it. E.g., when starting the application the label bounds have a width/height of 0, but if a label is repositioned by the user, the width/height is correct.
Is there a possibility to force JavaFX to draw the current elements? The code is rather complex, but the following gives an idea of what I want to do:
stackpane = new StackPane();
text = new Label("Test");
text.setStyle("-fx-border-color:black; -fx-padding:3px;");
stackpane.getChildren().addAll(text);
...
// is it possible to force JavaFX to draw the text here?
...
// some calculations with the bounds of the label
Node node = getLabel();
Bounds bounds = node.getBoundsInParent();
double height = bounds.getHeight();
double width = bounds.getWidth();
I also tried to wrap the text in a rectangle and then manually set the width/height of the rectangle. This works, but the nodes have labels of different length and thus manually setting it is not always suitable.
You may try to bind your logic to node.boundsInParentProperty() changes
public class SmartBorder extends Application {
#Override
public void start(Stage primaryStage) {
final Label txt = new Label("Example");
txt.relocate(100, 100);
Pane root = new Pane();
final Rectangle border = new Rectangle();
border.setFill(Color.TRANSPARENT);
border.setStroke(Color.RED);
// here we autoupdate border
txt.boundsInParentProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> ov, Bounds old, Bounds b) {
border.setX(b.getMinX() - 1);
border.setY(b.getMinY() - 1);
border.setWidth(b.getWidth()+2);
border.setHeight(b.getHeight()+2);
}
});
root.getChildren().addAll(txt, border);
// click to see automatic border reaction
root.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
txt.relocate(Math.random()*200, Math.random()*200);
txt.setText(Math.random() + "");
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
}