JavaFX - BorderPane/StackPane not resizing after children changed - java

I am having a resize issue when content is added and removed from a JavaFX BorderPane. The BorderPane content is not being resized until the window is manually resized. I have written a small test app to model this behavior. The application builds a BorderPane that contains a rectangle embedded within a StackPane at the center of the BorderPane. Along the bottom of the BorderPane there is a VBox and HBox that contain text and a separator. There is a menu item that removes the content from the bottom of the BorderPane (MoveText -> Right) and adds similar content to the right position of the BorderPane.
When the text is added to the right position of the BorderPane, the rectangle overlaps the text. In otherwords the content of the BorderPane center is overlapping the content in the BorderPane right.
I saw the following link -
https://stackoverflow.com/questions/5302197/javafx-bug-is-there-a-way-to-force-repaint-this-is-not-a-single-threading-pr
Calling requestLayout does not seem to help. I have also tried calling impl_updatePG and impl_transformsChanged on various nodes in the graph. I got this idea from this thread - https://forums.oracle.com/forums/thread.jspa?threadID=2242083
public class BorderPaneExample extends Application
{
private BorderPane root;
private StackPane centerPane;
#Override
public void start(Stage primaryStage) throws Exception
{
root = new BorderPane();
root.setTop(getMenu());
root.setBottom(getBottomVBox());
centerPane = getCenterPane();
root.setCenter(centerPane);
Scene scene = new Scene(root, 900, 500);
primaryStage.setTitle("BorderPane Example");
primaryStage.setScene(scene);
primaryStage.show();
}
private MenuBar getMenu()
{
MenuBar menuBar = new MenuBar();
MenuItem rightMenuItem = new MenuItem("Right");
rightMenuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
root.setRight(getRightHBox());
root.setBottom(null);
root.requestLayout();
}
});
MenuItem bottomMenuItem = new MenuItem("Bottom");
bottomMenuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
root.setRight(null);
root.setBottom(getBottomVBox());
}
});
Menu menu = new Menu("Move text");
menu.getItems().add(rightMenuItem);
menu.getItems().add(bottomMenuItem);
menuBar.getMenus().addAll(menu);
return menuBar;
}
private HBox getRightHBox()
{
HBox hbox = new HBox();
VBox vbox = new VBox(50);
vbox.setPadding(new Insets(0, 20, 0, 20));
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().addAll(new Text("Additional Info 1"),
new Text("Additional Info 2"), new Text("Additional Info 3"));
hbox.getChildren().addAll(new Separator(Orientation.VERTICAL), vbox);
return hbox;
}
private VBox getBottomVBox()
{
VBox vbox = new VBox();
HBox hbox = new HBox(20);
hbox.setPadding(new Insets(5));
hbox.setAlignment(Pos.CENTER);
hbox.getChildren().addAll(new Text("Footer Item 1")
, new Text("Footer Item 2"), new Text("Footer Item 3"));
vbox.getChildren().addAll(new Separator(), hbox);
return vbox;
}
private StackPane getCenterPane()
{
StackPane stackPane = new StackPane();
stackPane.setAlignment(Pos.CENTER);
final Rectangle rec = new Rectangle(200, 200);
rec.setFill(Color.DODGERBLUE);
rec.widthProperty().bind(stackPane.widthProperty().subtract(50));
rec.heightProperty().bind(stackPane.heightProperty().subtract(50));
stackPane.getChildren().addAll(rec);
return stackPane;
}
public static void main(String[] args)
{
Application.launch(args);
}
}
Any suggestions would be appreciated.
Thanks

For the sake of having an answer:
The StackPane needs to have its minimum size set in order to do clipping. So if you explicitly add stackPane.setMinSize(0, 0); to the getCenterPane() method, it should fix your problem.
So your getCenterPane() method would now look like this:
private StackPane getCenterPane()
{
StackPane stackPane = new StackPane();
stackPane.setMinSize(0, 0);
stackPane.setAlignment(Pos.CENTER);
final Rectangle rec = new Rectangle(200, 200);
rec.setFill(Color.DODGERBLUE);
rec.widthProperty().bind(stackPane.widthProperty().subtract(50));
rec.heightProperty().bind(stackPane.heightProperty().subtract(50));
stackPane.getChildren().addAll(rec);
return stackPane;
}

Related

JavaFX: Multiple 'views', 'pages' or scenes - how to approach?

I am trying to build a dummy webshop with JavaFX. I deliberately do not use fxml, scenebuilder, maven or any other build tool. Just plain JavaFX, in order to really get to understand the basics.
However, I ran into a problem creating and navigating different 'pages'.
I have tried various creative solutions, like this one (is posting links allowed?), but none fully work for me, as I want every 'page', 'view' or scene in a seperate java class file, in order to keep everything structured and orderly.
I figured I'd make a Borderpane as a parent layout for every page
abstract class WindowBase extends BorderPane {
public abstract BorderPane render(App app);
public WindowBase() {
Label labelTop = new Label("Top box");
HBox topBox = new HBox();
topBox.setStyle("-fx-background-color: red;");
topBox.getChildren().addAll(labelTop);
Label labelLeft = new Label("Left box");
VBox leftBox = new VBox();
leftBox.setStyle("-fx-background-color: green;");
leftBox.getChildren().addAll(labelLeft);
Label labelRight = new Label("Right box");
VBox rightBox = new VBox();
rightBox.setStyle("-fx-background-color: blue;");
rightBox.getChildren().addAll(labelRight);
Label labelBottom = new Label("Bottom box");
HBox bottomBox = new HBox();
bottomBox.setStyle("-fx-background-color: yellow;");
bottomBox.getChildren().addAll(labelBottom);
this.setTop(topBox);
this.setLeft(leftBox);
this.setRight(rightBox);
this.setBottom(bottomBox);
}
}
and a child, the home page
public class Homepage extends WindowBase {
public BorderPane render(App app) {
Button button = new Button("Go to shopping cart");
button.setOnAction((event) -> app.toShoppingCart());
StackPane centerPane = new StackPane();
centerPane.getChildren().add(button);
this.setCenter(centerPane);
return this;
}
}
and lastly my App.java that runs everything
public class App extends Application{
private WindowBase view;
public void start(Stage stage) throws Exception {
view = new Homepage();
stage.setScene(new Scene(view.render(this)));
stage.setFullScreen(true);
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
public void toHomepage() {
this.view = new Homepage();
}
public void toShoppingCart() {
this.view = new ShoppingCart();
}
}
I understand that I can't pass this (App) as an argument to view.render(), use the parameter within the method render and expect to be able to manipulate it, because it only creates a new instance of App as soon as it gets there. However, I see no other way either.
I tried placing the navigation buttons in the App class, in order to be able to manipulate view, but then I cannot call on the buttons from the subsequent views.
There must be a way to achieve what I want without writing the complete GUI in one file, right? Should I make my view static in stead, is that it?
Instead of BorderPanes I am of course also okay with using Scenes, whatever works.
I have figured out exactly the solution that I wanted. Posting it here for whoever encounters the same situation.
Class WindowBase
public class WindowBase {
public BorderPane getMainPane() {
Label labelTop = new Label("Top box");
HBox topBox = new HBox();
topBox.setStyle("-fx-background-color: red;");
topBox.getChildren().addAll(labelTop);
Label labelLeft = new Label("Left box");
VBox leftBox = new VBox();
leftBox.setStyle("-fx-background-color: green;");
leftBox.getChildren().addAll(labelLeft);
Label labelRight = new Label("Right box");
VBox rightBox = new VBox();
rightBox.setStyle("-fx-background-color: blue;");
rightBox.getChildren().addAll(labelRight);
Label labelBottom = new Label("Bottom box");
HBox bottomBox = new HBox();
bottomBox.setStyle("-fx-background-color: yellow;");
bottomBox.getChildren().addAll(labelBottom);
BorderPane borderPane = new BorderPane();
borderPane.setTop(topBox);
borderPane.setLeft(leftBox);
borderPane.setRight(rightBox);
borderPane.setBottom(bottomBox);
return borderPane;
}
}
Class Homepage
public class Homepage extends WindowBase {
public BorderPane render(Button toShoppingCart) {
Label label = new Label("This is the homepage.");
label.setStyle(
"-fx-font: normal bold 30px 'elephant'; -fx-text-fill: black; -fx-background-color: red;");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(label);
BorderPane mainPane = getMainPane();
VBox leftBox = (VBox) mainPane.getLeft();
leftBox.getChildren().add(toShoppingCart);
//I do not understand why this works. I abstracted leftBox from mainPane, then added shoppingCart, but never added the abstracted
// leftBox back to the mainPane before returning it. This should not work, but it does. leftBox.getChildren().add(toShoppingCart)
// should have no effect.
//However, mainPane.getChildren().add(leftBox) throws an IllegalArgumentException about duplicate components, which is to be
// expected if the leftBox is already automatically added back to the mainPane.
mainPane.setCenter(stackPane);
return mainPane;
}
}
Class ShoppingCart
public class ShoppingCart extends WindowBase {
public ShoppingCart() {
super();
}
public BorderPane render(Button toHomepage) {
Label label = new Label("This is the shopping cart.");
label.setStyle(
"-fx-font: normal bold 30px 'elephant'; -fx-text-fill: black; -fx-background-color: red;");
StackPane centerPane = new StackPane();
centerPane.getChildren().add(label);
BorderPane mainPane = getMainPane();
VBox leftBox = (VBox) mainPane.getLeft();
leftBox.getChildren().add(toHomepage);
mainPane.setCenter(centerPane);
return mainPane;
}
}
Class App
public class App extends Application{
Scene scene;
#Override
public void start(Stage stage) throws Exception {
Homepage homePane = new Homepage();
Button toHomepage = new Button("Back to home page");
ShoppingCart shoppingPane = new ShoppingCart();
Button toShoppingCart = new Button("To shopping cart");
toHomepage.setOnAction(e -> scene.setRoot(homePane.render(toShoppingCart)));
toShoppingCart.setOnAction(e -> scene.setRoot(shoppingPane.render(toHomepage)));
scene = new Scene(homePane.render(toShoppingCart), 600, 400);
stage.setScene(scene);
stage.setFullScreen(true);
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}

JavaFX ScrollPane setVvalue() not working properly

I am testing the JavaFX ScrollPane class and realized that it is not working as I expect, I don't know why. I have the following code:
public class Client3 extends Application {
int indexMsg = 0;
Button send;
GridPane root;
ScrollPane msgPane;
GridPane msgPaneContent;
FlowPane writePane;
TextField writeMsg;
Scene scene;
#Override
public void start(Stage primaryStage) {
root = new GridPane();
root.setAlignment(Pos.CENTER);
root.setVgap(10);
root.setPadding(new Insets(10, 10, 10, 10));
msgPane = new ScrollPane();
msgPane.setPrefSize(280, 280);
msgPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
msgPaneContent = new GridPane();
msgPaneContent.setPrefWidth(270);
msgPaneContent.setVgap(10);
writePane = new FlowPane(10, 10);
writePane.setAlignment(Pos.CENTER);
writePane.setPrefWidth(280);
writeMsg = new TextField();
writeMsg.setPrefWidth(150);
writeMsg.setPromptText("Write your message");
writePane.getChildren().add(writeMsg);
GridPane.setConstraints(msgPane, 0, 0);
GridPane.setConstraints(writePane, 0, 1);
msgPane.setContent(msgPaneContent);
root.getChildren().addAll(msgPane, writePane);
writeMsg.setOnAction((ev) -> {
if (!writeMsg.getText().isEmpty()) {
TextArea msg = new TextArea(writeMsg.getText());
msg.setMaxWidth(135);
msg.setPrefRowCount(msg.getLength() / 21 + 1);
msg.setWrapText(true);
GridPane.setConstraints(msg, 0, indexMsg);
indexMsg++;
writeMsg.deleteText(0, writeMsg.getText().length());
msgPaneContent.getChildren().add(msg);
msgPane.setVvalue(1.0);
}
});
scene = new Scene(root, 300, 300);
primaryStage.setTitle("Chat App");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Basically, I have a GridPane as the root with a ScrollPane and a GridPane as its children. The ScrollPane has a children GridPane. There is a TextField with an EventHandler which generates a TextArea inside the GridPane (the ScrollPane's children). Each TextArea object is created in the vertical direction, downwards. I want to set the scrollbar always at its maximum value (setVvalue(1.0)) each time a new TextArea is added. The thing is that it doesn't seem to work as it should because the vertical value is never set to the maximum after handling the event, but it seems to be set to the maximum value that it had before handling it (the bottom of the previous TextArea added).
Any solution for this? Thanks in advance.

How to call a method when a button is clicked in JavaFX?

I'm having a problem with creating a method and calling it when a button is clicked.
What I want to do is when a button is clicked the scene changes to a new one. I have already achieved this but I came across a problem when I need to create more of such actions.
Here is my code:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane borderPane = new BorderPane();
Button dugmeStart = new Button("Start");
dugmeStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
BorderPane borderPane2 = new BorderPane();
Label prvo_pritanje = new Label("Prvo pitanje: Kada je poceo Prvi svetski rat?");
prvo_pritanje.setStyle("-fx-font-weight: bold;");
Button dalje1 = new Button("Dalje");
//Navigacija
HBox navigacija_goranj = new HBox();
navigacija_goranj.getChildren().add(prvo_pritanje);
navigacija_goranj.setAlignment(Pos.CENTER);
navigacija_goranj.setPadding(new Insets(15,12, 15, 12));
HBox navigacija_donja = new HBox();
navigacija_donja.getChildren().add(dalje1);
navigacija_donja.setPadding(new Insets(15,12, 15, 12));
navigacija_donja.setAlignment(Pos.CENTER);
//Odgovori
HBox odgovori = new HBox();
ToggleGroup pitanja1 = new ToggleGroup();
RadioButton prvo_pitanje = new RadioButton("1914");
prvo_pitanje.setToggleGroup(pitanja1);
RadioButton drugo_pitanje = new RadioButton("1918");
drugo_pitanje.setToggleGroup(pitanja1);
RadioButton trece_pitanje = new RadioButton("1910");
trece_pitanje.setToggleGroup(pitanja1);
odgovori.getChildren().addAll(prvo_pitanje, drugo_pitanje, trece_pitanje);
odgovori.setSpacing(15);
odgovori.setAlignment(Pos.CENTER);
//Dodavanje u BorderPane
borderPane2.setBottom(navigacija_donja);
borderPane2.setTop(navigacija_goranj);
borderPane2.setCenter(odgovori);
Scene scena2 = new Scene(borderPane2, 300, 300);
primaryStage.setScene(scena2);
}
});
Label dobrodoslica = new Label("Pocetak kviza");
dobrodoslica.setStyle("-fx-font-weight: bold;");
//Definisanje Hbox-a sa dugmetom "Start"
HBox navigacija1 = new HBox();
navigacija1.setPadding(new Insets(15, 12, 15, 12));
navigacija1.getChildren().add(dugmeStart);
navigacija1.setAlignment(Pos.CENTER);
borderPane.setBottom(navigacija1);
borderPane.setCenter(dobrodoslica);
Scene scene = new Scene(borderPane,300,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("Domaci: Kviz");
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
You can see that when a button is clicked it creates a whole new scene and BorderPane, and populates it with content. I figured that it would be best to create a method with each scene and then call each method when a button is clicked. The problem is I cant figure out how to do this.
Any help is appreciated. Thanks in advance.

How to add a ScrollBar in JavaFx

I'm trying to add a ScrollBar to a HBox. The ScrollBar gets added, but I get no scrolling. How can I make it work?
public class ScrollableItems {
public void scrollableItems(HBox content) {
double height = 180;
ScrollBar sc = new ScrollBar();
content.getChildren().add(sc);
sc.setLayoutX(content.getWidth() - sc.getWidth());
sc.setMin(0);
sc.setOrientation(Orientation.VERTICAL);
sc.setPrefHeight(height);
sc.setMax(height * 2);
sc.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
content.setLayoutY(-new_val.doubleValue());
}
});
}
}
Add children to a HBox then pass it to scrollableItems(HBox content) above to add a SCrollBar
public HBox mainItemsWrapper() {
HBox scrollabelWrapper = new HBox();
scrollabelWrapper.setMaxHeight(180);
HBox entityDetailViewWrapper = new HBox();
entityDetailViewWrapper.getChildren().addAll(.....);
scrollabelWrapper.getChildren().add(entityDetailViewWrapper);
new ScrollableItems().scrollableItems(scrollabelWrapper);
return scrollabelWrapper;
}
Thank you all.....
I do not really get why you are trying to reinvent the wheel.. you should probably use the ScrollPane instead.
This little example shows how to create a horizontally scrollable HBox with the ScrollPane class:
#Override
public void start(Stage primaryStage) {
HBox hbox = new HBox();
Button b = new Button("add");
b.setOnAction(ev -> hbox.getChildren().add(new Label("Test")));
ScrollPane scrollPane = new ScrollPane(hbox);
scrollPane.setFitToHeight(true);
BorderPane root = new BorderPane(scrollPane);
root.setPadding(new Insets(15));
root.setTop(b);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
As eckig said, you can wrap you HBox into a ScrollPane.
Additionnaly, you can customize the visual part of the scrollbar in CSS. I found this link useful for the comprehension of the differents parts of a scrollbar: Customize ScrollBar via CSS

How to force ScrollPane fill parent vbox

I have ListView inside ScrollPane and ScrollPane inside VBox and all is in SplitPane. How to force ScrollPane to fit and fill VBox. It should by resistant for resizing scene and split pane. I tried do this by adding listener on scene resized and setup pref width and height of scroll pane (taken from parent VBox), but it's not working always.
I do everything in pure java and i want to avoid to use fxml.
Here is sample code
Somewhere in main class (extends Application):
private void setupLayout()
{
BorderPane lvBorderPane = new BorderPane();
String lvSplitPaneStyle =
"-fx-background-color: #FFFFFF;-fx-padding: 10px";
SplitPane lvMainContainer = new SplitPane();
lvMainContainer.setOrientation(Orientation.VERTICAL);
lvMainContainer.setStyle(lvSplitPaneStyle);
VBox mLeftContainer = new VBox();
mLeftContainer.setStyle("-fx-background-color: #000000");
mLeftContainer.setMinWidth(140);
mLeftContainer.setPrefWidth(220);
VBox lvCenterContainer = new VBox();
lvCenterContainer.setStyle("-fx-background-color: #000000;");
VBox lvRightContainer = new FooBarList(mScene);
HBox lvBottomContainer = new HBox();
lvBottomContainer.setStyle("-fx-background-color: #000000;");
lvBottomContainer.setMinHeight(40);
lvBottomContainer.setMaxHeight(40);
SplitPane lvTopPane = new SplitPane();
lvTopPane.setOrientation(Orientation.HORIZONTAL);
lvMainContainer.getItems().addAll(lvTopPane, lvBottomContainer);
lvTopPane.getItems().addAll(mLeftContainer, lvCenterContainer, lvRightContainer);
lvBorderPane.setCenter(lvMainContainer);
mRoot.getChildren().add(lvBorderPane);
}
FooBarList class:
public class FooBarList extends VBox
{
public FooBarList(Scene pmScene)
{
this.mWindow = pmScene;
setupLayout();
}
private void setupLayout()
{
this.setStyle("-fx-background-color: #000000;");
this.setMinWidth(240);
this.setPrefWidth(240);
mContainer = new ListView<String>();
ObservableList<String> lvItems = FXCollections.observableArrayList("");
this.mContainer.setItems(lvItems);
this.mScrollPane = new ScrollPane(mContainer);
this.getChildren().add(mScrollPane);
this.mScrollPane.setFitToHeight(true);
this.mScrollPane.setFitToWidth(true);
final VBox lvParent = this;
mWindow.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
mScrollPane.setPrefWidth(lvParent.getWidth());
}
});
mWindow.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
mScrollPane.setPrefHeight(lvParent.getHeight());
}
});
}
private ScrollPane mScrollPane;
private ListView<String> mContainer;
private Scene mWindow;
}

Categories