I'm relatively new to Java and I'm having difficulty with running a program. Now, as a heads up, this is a homework assignment. The problem is to create a program with the output as "Welcome to Java" in a circle.
Here is my code thus far:
import java.awt.Color;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.Parent;
import javafx.scene.layout.Pane;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class Characters extends Application {
public void start(Stage stage) {
Pane canvas = new Pane();
canvas.setStyle("-fx-background-color: black;");
canvas.setPrefSize(200, 200); // set size of pane
Font f = Font.font("Times New Roman", FontWeight.BOLD, 35);
String s = "Welcome to Java";
String c;
double d = 25.0, x = 10.0, y = 20.0;
for (int i = 0; i < s.length(); i++) {
c = "" + s.charAt(i);
Text t = new Text(x, y, c);
t.setFont(f);
t.setRotate(d);
d++;
x++;
y++;
canvas.getChildren().add(t);
}
Scene scene = new Scene(root,500, 500, Color.BLACK);
stage.setTitle("Characters around a circle");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I keep receiving an error at Scene scene = new Scene(root,500, 500, Color.BLACK); and I haven't been able to find a solution. Any help is appreciated. Thanks.
root is not declared anywhere in your program. Try adding canvas to a layout and add the layout to the scene.
BorderPane rootLayout = new BorderPane();
rootLayout.getChildren().add(canvas);
Scene scene = new Scene(rootLayout, 500,500);
It doesn't look like root (the first parameter in your constructor) is defined anywhere in the scope.
In the docs they do this:
Group root = new Group();
Scene s = new Scene(root, 300, 300, Color.BLACK);
But I'm not sure if you want to put in the canvas somewhere.
Something to note (copy-pasted from the docs):
The application must specify the root Node for the scene graph by setting the root property. If a Group is used as the root, the contents of the scene graph will be clipped by the scene's width and height and changes to the scene's size (if user resizes the stage) will not alter the layout of the scene graph. If a resizable node (layout Region or Control is set as the root, then the root's size will track the scene's size, causing the contents to be relayed out as necessary.
Basically, if you want the components to be forced within the Scene, it looks like you want a Group. If you want the components to change the size of the Scene then use a resizable node (e.g. Region or Control).
Related
package example;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Text text = new Text("This is a Text");
VBox box = new VBox();
box.setAlignment(Pos.CENTER);
box.setStyle("-fx-background-color: yellow;");
box.getChildren().add(text);
StackPane container = new StackPane();
container.getChildren().add(box);
BorderPane bp = new BorderPane();
bp.setCenter(container);
Scene scene = new Scene(bp, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Here's the output:
Question: Can someone explain to me why the Vbox fill the whole screen? Is there a method that is similar to Android's wrap_content? I want the image below to be the output:
Solution
Wrap the VBox in a Group; e.g. use:
container.getChildren().add(new Group(box));
instead of:
container.getChildren().add(box);
Why it works
From the Group javadoc:
By default, a Group will "auto-size" its managed resizable children to their preferred sizes during the layout pass.
This means that the VBox won't grow past the preferred size of it's content (which is just enough area to display the label inside it).
Alternate implementation
Set the maximum size of the VBox to the preferred size. Then the VBox will only ever grow large enough to fit the preferred size of the content inside it and will never grow any larger.
box.setMaxSize(VBox.USE_PREF_SIZE, VBox.USE_PREF_SIZE);
Why VBox grows by default
It is a resizable container which will stretch to fill available area.
Note
I don't know that the effect is exactly the same as an Android wrap_content method as I have never developed for Android, however the effect does seem to exactly match the second image you provided in your question, which appears to be what you want.
VBox automatically resizes itself to the size of the Parent, so it is better not to set background color to it. Instead, you can use a Label in place of a Text and then add background color to the Label instead of the VBox.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Label text = new Label("This is a Text");
VBox box = new VBox();
box.setAlignment(Pos.CENTER);
text.setStyle("-fx-background-color: yellow;");
box.getChildren().add(text);
StackPane container = new StackPane();
container.getChildren().add(box);
BorderPane bp = new BorderPane();
bp.setCenter(container);
Scene scene = new Scene(bp, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
This will give you an output like an image below:
I currently have a ScrollPane with a FlowPane as content. The FlowPane currently initializes with no children nodes, a fixed width and a pref/min height (but no max height).
While adding items to the FlowPane at runtime (I click some UI element and something is added to the FlowPane), the ScrollPane should adjust its height in the case that the addition to the FlowPane no longer fits.
I don't understand how to set the height of the flowPane and ScrollPane so that this works - if that's the problem to begin with. At the moment, whenever the addition to the FlowPane doesn't fit its initial height, the content is added, but not visible. The scrollbar belonging to the ScrollPane never adjusts its height - if it did, I could just scroll further down and see the content.
Let's say I have a ScrollPane with some width and height, some viewport width/height, and a FlowPane with some width/height - What should my settings be for the min/pref/max sizes? How can I make a scrollPane adjust its scrollbar behaviour or make the content visible?
The ScrollPane's setFitToHeight is already set to true, which didn't seem to change anything.
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class FlowPaneTest extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
// borderPane rootPane
BorderPane borderPane = new BorderPane();
borderPane.setMinSize(600, 600);
borderPane.setPrefSize(600, 600);
borderPane.setMaxSize(600, 600);
// container for the two scrollPanes below
FlowPane flow = new FlowPane();
borderPane.setRight(flow);
// two scrollPanes, each should resize it's height (width should be fixed) if
// children are added beyond it's current height
ScrollPane top = new ScrollPane();
ScrollPane bottom = new ScrollPane();
FlowPane scrollPaneContent = new FlowPane();
top.setContent(scrollPaneContent);
bottom.setContent(scrollPaneContent);
flow.getChildren().add(top);
flow.getChildren().add(bottom);
borderPane.setOnMouseClicked(new EventHandler<Event>()
{
#Override
public void handle(Event event)
{
Label l = new Label("test");
l.setMinSize(100, 100);
l.setPrefSize(100, 100);
l.setMaxSize(100, 100);
scrollPaneContent.getChildren().add(l);
}
});
// size settings
int width = 300, height = 300;
top.setHvalue(0.5);
top.setMinViewportHeight(height);
top.setPrefViewportHeight(height);
top.setMinViewportWidth(width);
top.setPrefViewportWidth(width);
top.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
top.setMinSize(width, height);
top.setPrefSize(width, height);
top.setMaxWidth(width);
scrollPaneContent.setMinSize(width, height);
scrollPaneContent.setPrefSize(width, height);
scrollPaneContent.setMaxWidth(width);
scrollPaneContent.setPrefHeight(height);
bottom.setMinSize(width, height);
bottom.setPrefSize(width, height);
bottom.setMaxWidth(width);
bottom.setHvalue(0.5);
bottom.setMinViewportHeight(height);
bottom.setPrefViewportHeight(height);
bottom.setMinViewportWidth(width);
bottom.setPrefViewportWidth(width);
bottom.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
bottom.setFitToHeight(true);
// stage
Scene scene = new Scene(borderPane, 600.0, 600.0);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Try to give the ScrollPane pref height and width and add this line
scrollPane.setFitToWidth(true);
Ending up with something similar to this ugly bit of code - It listens to the number of children in the pane and increases the size every time something is added to the list of children:
topSubPane.getChildren().addListener(new ListChangeListener()
{
#Override
public void onChanged(Change c)
{
c.next();
topSubPane.setPrefHeight(topSubPane.getHeight() + 50);
}
});
Works, but feels like an unorthodox hack. Is there really no regular way of doing this?
I have a draggable button class that displays a button to my screen which can be moved around with a mouse drag. However each draggable button is in a separate stage->scene->pane->node in order to show each individually.
is there a way to add multiple draggable buttons to my screen without having a millions stages open?
I thought maybe having one transparent stage the size of the screen?
But i don't see how that would work if I can only show one scene at a time.
//Draggable button class
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class DraggableButton extends Button{
private double xOffset = 0;
private double yOffset = 0;
public DraggableButton(Stage stage){
this.setOnMousePressed(event -> {
xOffset = event.getSceneX();
yOffset = event.getSceneY();
});
this.setOnMouseDragged(event -> {
stage.setX(event.getScreenX() - xOffset);
stage.setY(event.getScreenY() - yOffset);
});
}
}
//Draggable button object
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ButtonDragV2 {
private ButtonCollection btnCollection = ButtonCollection.getInstance();
private Stage stage;
private DraggableButton btn;
private VBox root;
private Scene scene;
private ButtonStacker bs;
public ButtonDragV2(){
stage = new Stage();
btn = new DraggableButton(stage);
root = new VBox();
scene = new Scene(root);
bs = new ButtonStacker();
stage.initStyle(StageStyle.UNDECORATED);
root.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btn.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btn.setPrefSize(100,100);
VBox.setVgrow(btn, Priority.ALWAYS);
root.getChildren().add(btn);
...
stage.show();
btnCollection.addButton(this);
{
{
Fro each dialog you create if you set the:
stage.initOwner(mainStage);
to the same initOwner there will only be one icon
The problem here is that you are instantiating a new stage, node, scene, etc. each time you create a new ButtonDragV2. What you want to do is instead inject he dependencies in the constructor like so:
public class ButtonDragV2 {
...
private Stage stage;
private VBox root;
private Scene scene;
public ButtonDragV2(State stage, VBox root, Scene scene, ...){
this.stage = stage;
this.root = root;
this.scene = scene;
...
}
}
Note that the ... are not correct syntax, I'm simply putting these here to show that you could potentially inject more dependencies if possible (can't say for sure what they should be since I'm not familiar with javafx).
Finally, in my opinion you should just inject the root in the constructor and add your button to it, I don't see a reason why you are injecting your scene and stage since you don't seem to be interacting with them at all. You also shouldn't be calling stage.show() from your button class since it's not really appropriate to do so from here.
PS: you should probably look at a couple of Object Oriented programming tutorials to wrap your head around how it works and how to better implement it in your code :) .
You can show multiple Buttons in the same Scene.
Example
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
root.setBackground(null);
Node[] btns = new Node[5];
for (int i = 0; i < btns.length; i++) {
btns[i] = new Button("Button "+(i+1));
}
btns[0].relocate(10, 10);
btns[1].relocate(400, 10);
btns[2].relocate(200, 200);
btns[3].relocate(10, 400);
btns[4].relocate(400, 400);
root.getChildren().addAll(btns);
Scene scene = new Scene(root);
scene.setFill(null);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.setMaximized(true);
primaryStage.setScene(scene);
primaryStage.show();
}
I'm writing a GUI application with a ScrollPane, but had some issues with resizing. I extracted the essential code in the following example:
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import javafx.scene.layout.VBox;
import javafx.scene.control.ScrollPane;
public class JavaFXExample extends Application {
final int width = 300;
final int height = 300;
#Override
public void start(Stage primaryStage) {
Button b = new Button("This should be at the bottom!");
//this vbox goes inside the scrollpane
VBox boxInScrollPane = new VBox(10);
boxInScrollPane.setAlignment(Pos.BOTTOM_CENTER);
boxInScrollPane.getChildren().add(b);
//main content
ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(boxInScrollPane);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
//Doesn't do anything!
scrollPane.setPrefSize(100, 100);
scrollPane.setMaxSize(100, 100);
scrollPane.setMinSize(100, 100);
Scene scene = new Scene(scrollPane, width, height);
primaryStage.setScene(scene);
primaryStage.show();
//set size of boxInScrollPane to be equal to the viewport
Bounds viewportBounds = scrollPane.getViewportBounds();
double innerWidth = viewportBounds.getMaxX() - viewportBounds.getMinX();
double innerHeight = viewportBounds.getMaxY() - viewportBounds.getMinY();
System.out.println(innerWidth + " " + innerHeight);
boxInScrollPane.setPrefSize(innerWidth, innerHeight);
}
public static void main(String[] args) {
launch(args);
}
}
So I have a window, which contains a ScrollPane, which contains a VBox, which contains a button. The example here, where I resize the scrollpane to be 100x100px in a 300x300px window, is arbitrary. What's important is that when I run this code, I get a scrollpane that fills the entire window! Here's my output:
What's going on here?
The root of the scene is sized to fill the entire scene, irrespective of its min/pref/max size. If you want the ScrollPane to remain 100 pixels wide and 100 pixels high, wrap it in another container (pretty much any container will do); the container will then be resized, but the ScrollPane will respect its layout sizes:
Scene scene = new Scene(new StackPane(scrollPane), width, height);
By default a StackPane centers its content, so this results in
Solution:
Pane pane = new Pane(scrollPane);
Scene scene = new Scene(pane, width, height);
primaryStage.setScene(scene);
primaryStage.show();
From Scene constuctor doc:
Creates a Scene for a specific root Node with a specific size.
Setting ScrollPane as root node will make it resize to given size in constructor so the previous settings will not work.
Solution will be to make a simple pane that will be resized so ScrollPane will be on his own rules.
I would like to have a window that shows an image. That is the main purpose of the window. However it should be possible to also have controls on the top. The number is not known beforehand. Could be 3 or 15. They should just pile up there for now. So the upper part grows and the image below is just being pushed down.
void createNewWindow() {
Stage stage = new Stage();
BorderPane pane = new BorderPane();
ImageView imageView = new ImageView("path");
pane.setCenter(imageView);
HBox controlBox = new HBox(10);
pane.setTop(controlBox);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
}
This code barely works. I have to add the width and height manually because the scene or the stage doesn't look for anything to fit to. And when I am adding buttons later to the HBox on the top the window doesn't increase in size, neither does the HBox (height stays at 0). Only the image gets pushed down it is not fully visible anymore.
How would I go about this instead?
You should use the HBox.setHgrow method on each of the children of controlBox.
// for each button
HBox.setHgrow(child, Priority.ALWAYS);
This will align the buttons next to each other, reducing the sizes so that all of them fit in one line and they fill the available space.
JavaFX nodes are dynamically re-sizable i.e. the child will fill the space provided by parent and Parent will expand in accordance to the minimum space the child needs.
I don't face issue that have been raised by you while trying to add a HBox to a BorderPane. The BorderPane height increased (more than the image height) after the HBox got added to it. In case you want to see if the Image gets pushed down, try replacing a VBox to the HBox.
A simple example where I use an image of 365 and a HBox of 26, which result in a BorderPane of 391 height
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class BorderPaneHeight extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
HBox box = new HBox(10);
ImageView imageView = new ImageView(new Image("file:///home/itachi/Pictures/aaa.png")); // Replace with your image path
Button button1 = new Button("Add");
Button button2 = new Button("Add");
box.getChildren().addAll(button1, button2);
borderPane.setTop(box);
borderPane.setCenter(imageView);
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("Image height : " + imageView.getImage().getHeight());
System.out.println("Hbox height : " + box.getHeight());
System.out.println("BorderPane Height : " + borderPane.getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Output on Console
Image height : 365.0
Hbox height : 26.0
BorderPane Height : 391.0
For the window to grow as the content inside the window expands I did like this(looked but haven't found another solution)
In this case a new window is open from MainController and this window contents can grow and I want this new window to grow with it, so in the controller for the new window I add a listener...
containerPane.heightProperty().addListener((observable, oldValue, newValue) -> {
MainController.theStage.setHeight(MainController.theStage.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
});