JavaFX 2.0 + FXML - strange lookup behaviour - java

I want to find a VBox node in a scene loaded with FXMLoader thanks to Node#lookup() but I get the following exception :
java.lang.ClassCastException: com.sun.javafx.scene.control.skin.SplitPaneSkin$Content cannot be cast to javafx.scene.layout.VBox
The code :
public class Main extends Application {
public static void main(String[] args) {
Application.launch(Main.class, (java.lang.String[]) null);
}
#Override
public void start(Stage stage) throws Exception {
AnchorPane page = (AnchorPane) FXMLLoader.load(Main.class.getResource("test.fxml"));
Scene scene = new Scene(page);
stage.setScene(scene);
stage.show();
VBox myvbox = (VBox) page.lookup("#myvbox");
myvbox.getChildren().add(new Button("Hello world !!!"));
}
}
The fxml file:
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" >
<children>
<SplitPane dividerPositions="0.5" focusTraversable="true" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" />
<VBox fx:id="myvbox" prefHeight="398.0" prefWidth="421.0" />
</items>
</SplitPane>
</children>
</AnchorPane>
I would like to know :
1. Why lookup method return a SplitPaneSkin$Content and not a VBox ?
2. How I can get the VBox in another manner ?
Thanks in advance

The easiest way to get a reference to the VBox is by calling FXMLLoader#getNamespace(). For example:
VBox myvbox = (VBox)fxmlLoader.getNamespace().get("myvbox");
Note that you'll need to create an instance of FXMLLoader and call the non-static version of load() in order for this to work:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("test.fxml"));
AnchorPane page = (AnchorPane) fxmlLoader.load();

SplitPane puts all items in separate stack panes (fancied as SplitPaneSkin$Content). For unknown reason FXMLLoader assign them the same id as root child. You can get VBox you need by next utility method:
public <T> T lookup(Node parent, String id, Class<T> clazz) {
for (Node node : parent.lookupAll(id)) {
if (node.getClass().isAssignableFrom(clazz)) {
return (T)node;
}
}
throw new IllegalArgumentException("Parent " + parent + " doesn't contain node with id " + id);
}
and use it next way:
VBox myvbox = lookup(page, "#myvbox", VBox.class);
myvbox.getChildren().add(new Button("Hello world !!!"));
you can use Controller and add autopopulated field:
#FXML
VBox myvbox;

Related

Order of the elements in the Anchor Pane

when I try to initialize the Anchor Pane a strange things happens: if I put the elements in this order I cannot click with the mouse, for example, on the elements of the list.
AnchorPane root = new AnchorPane(listLoader.load(), fieldLoader.load(), textareaLoader.load(), buttonLoader.load(), menuLoader.load());
But if I write it on this way I'm able to select the items on the list but not the items in the menubar:
AnchorPane root = new AnchorPane(fieldLoader.load(), textareaLoader.load(), buttonLoader.load(), menuLoader.load(), listLoader.load());
Do you know How should I write the elements to make at least the button, the menu and the list clickable?
This is the full code:
#Override
public void start(Stage stage) throws Exception {
FXMLLoader listLoader = new FXMLLoader(getClass().getResource("lista.fxml"));
FXMLLoader textareaLoader = new FXMLLoader(getClass().getResource("textarea.fxml"));
FXMLLoader fieldLoader = new FXMLLoader(getClass().getResource("textfield.fxml"));
FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("menubar.fxml"));
FXMLLoader buttonLoader = new FXMLLoader(getClass().getResource("button.fxml"));
AnchorPane root = new AnchorPane(listLoader.load(), fieldLoader.load(), textareaLoader.load(), buttonLoader.load(), menuLoader.load());
ListController listController = listLoader.getController();
TextAreaController textareaController = textareaLoader.getController();
TextFieldController fieldController = fieldLoader.getController();
MenuBarController menuController = menuLoader.getController();
ButtonController buttonController = buttonLoader.getController();
DataModel model = new DataModel();
listController.initModel(model);
textareaController.initModel(model);
fieldController.initModel(model);
menuController.initModel(model);
buttonController.initModel(model);
Scene scene = new Scene(root, 603, 403);
stage.setScene(scene);
stage.show();
}
Lista.fxml:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mailbox.ListController">
Button.fxml:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mailbox.ButtonController">
MenuBar.fxml:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="mailbox.MenuBarController">
<children>
<MenuBar fx:id="menuBar" layoutX="0.0" layoutY="0.0">
<menus>
<Menu text="File">
<items>
<MenuItem onAction="#elimina" text="Elimina" />
</items>
</Menu>
<Menu text="Cambia Account">
<items>
<MenuItem fx:id="email1" text="filippo#hotmail.it" />
<MenuItem fx:id="email2" text="giancarlo#yahoo.it" />
<MenuItem fx:id="email3" text="alessandro#gmail.it" />
</items>
</Menu>
</menus>
</MenuBar>
</children>
Textarea.fxml:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="mailbox.TextAreaController">
Textfield.fxml:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mouseTransparent="false" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mailbox.TextFieldController">
EDIT:
So should I declare more than one Anchor Pane and attach them to a main Anchor Pane?
AnchorPane root = new AnchorPane();
AnchorPane lista = new AnchorPane(listLoader.load());
AnchorPane textarea = new AnchorPane(textareaLoader.load());
AnchorPane field = new AnchorPane(fieldLoader.load());
AnchorPane menu = new AnchorPane(menuLoader.load());
AnchorPane button = new AnchorPane(buttonLoader.load());
root.getChildren().addAll(lista, textarea, field, menu, button);
EDIT2: This is the output of my program, can I create it with a BorderPane? Because It automatically anchor the elements on the right, left ecc... and for example I cannot put the textfield as you can see in the image
Probably consecutively loaded panes have the same sizes. After loading them you get a stack of panes with the same sizes. In that case only the top most pane (added as last one) is responsive for mouse events. I would suggest to provide correct sizes and anchors for every pane.
If my thesis is incorrect please provide more code.
EDIT: if child1 and child2 had the same sizes only the blue one would be visible but still the red one would be present but underneath and all covered by blue one. It's the same situation with your app. Btw you are misusing AnchorPane. AnchorPane is designated to anchor children.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class AnchorPaneTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
AnchorPane anchorPane = new AnchorPane();
AnchorPane child1 = new AnchorPane();
child1.setPrefSize(400., 600.);
child1.setStyle("-fx-background-color: red;");
AnchorPane child2 = new AnchorPane();
child2.setPrefSize(400., 300.);
child2.setStyle("-fx-background-color: blue;");
anchorPane.getChildren().addAll(child1, child2);
stage.setScene(new Scene(anchorPane));
stage.show();
}
}

Share model with nested controller

I'm trying to build a simple GUI with JavaFX using SceneBuilder, where I'm using a MenuItem (in Main.fxml) to select a root folder. The folder's contents are then listed in a TextArea that again is wrapped in a TabPane (FileListTab.fxml, nested FXML that is included in Main.fxml).
I used this post as a starting point to get used to MVC. Unfortunately I don't know how to make my nested FXML listen or be bound to the outer one since I'm not explicitly calling it. Right now I'm stuck just to display my chosen folder in a label.
My minimal working code right now looks like this:
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#browseInputFolder" text="Open folder" />
</items>
</Menu>
</menus>
</MenuBar>
</top>
<center>
<TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
<Tab text="File listing">
<content>
<fx:include fx:id="analysisTab" source="FileListTab.fxml" />
</content>
</Tab>
</tabs>
</TabPane>
</center>
</BorderPane>
FileListTab.fxml
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="15.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FileListController">
<children>
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Root folder:" />
<Label fx:id="label_rootFolder" />
</children>
</HBox>
<TextArea prefHeight="200.0" prefWidth="200.0" />
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Found files:" />
<Label fx:id="label_filesFound" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
Model.java (the supposed to be shared model between controllers)
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Model {
private StringProperty rootFolder;
public String getRootFolder() {
return rootFolderProperty().get();
}
public StringProperty rootFolderProperty() {
if (rootFolder == null)
rootFolder = new SimpleStringProperty();
return rootFolder;
}
public void setRootFolder(String rootFolder) {
this.rootFolderProperty().set(rootFolder);
}
}
NestedGUI.java (Main class)
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
public class NestedGUI extends Application {
Model model = new Model();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = null;
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getClassLoader().getResource("Main.fxml"));
root = (BorderPane) fxmlLoader.load();
MainController controller = fxmlLoader.getController();
controller.setModel(model);
// This openes another window with the tab's content that is actually displaying the selected root folder
/* FXMLLoader fxmlLoader2 = new FXMLLoader();
fxmlLoader2.setLocation(getClass().getClassLoader().getResource("FileListTab.fxml"));
VBox vBox = (VBox) fxmlLoader2.load();
FileListController listController = fxmlLoader2.getController();
listController.setModel(model);
Scene scene = new Scene(vBox);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();*/
} catch (IOException e) {
e.printStackTrace();
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
MainController.java
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
public class MainController {
Model model;
public void setModel(Model model) {
this.model = model;
}
public void browseInputFolder() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select folder");
File folder = chooser.showDialog(new Stage());
if (folder == null)
return;
String inputFolderPath = folder.getAbsolutePath() + File.separator;
model.setRootFolder(inputFolderPath);
System.out.print(inputFolderPath);
}
}
FileListController.java
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class FileListController {
Model model;
#FXML
Label label_rootFolder;
public void setModel(Model model) {
label_rootFolder.textProperty().unbind();
this.model = model;
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
I looked through various posts here on SO, but either I didn't understand the answers or others had different problems.
Can somebody give me some pointers? (hints to solve this, code snippets, links...) It looks like a pretty basic FXML-problem, but I just don't get it.
Simple solution
One option is just to inject the "nested controller" into the main controller as described in the FXML documentation.
The rule is that the field name for the controller should be the fx:id for the <fx:include> with the string "Controller" appended. So in your case, fx:id="analysisTab" and so the field will be FileListController analysisTabController. Once you have done that, you can pass the model to the nested controller when it is set in the main controller:
public class MainController {
Model model;
#FXML
private FileListController analysisTabController ;
public void setModel(Model model) {
this.model = model;
analysisTabController.setModel(model);
}
// ...
}
Advanced solution
One drawback to the simple solution above is that you have to propagate the model manually to all nested controllers, which can become tricky to maintain (especially if you have multiple levels of <fx:include>s). Another drawback is that you are setting the model after the controller is created and initialized (so, for example, the model is not available in the initialize() method, which is where you would most naturally like to use it).
A more advanced approach is to set a controllerFactory on the FXMLLoader. The controllerFactory is a function that maps the controller class (specified by the fx:controller attribute in the fxml file) to an object (almost always an instance of that class) that will be used as the controller. The default controller factory just invokes the no-argument constructor on the class. You can use this to invoke a constructor taking the model, so the model is available as soon as the controller is instantiated.
If you set a controller factory, the same controller factory is used for any included fxml files.
So you could rewrite your controllers to have constructors taking a model instance:
public class MainController {
private final Model model;
public MainController(Model model) {
this.model = model;
}
public void browseInputFolder() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select folder");
File folder = chooser.showDialog(new Stage());
if (folder == null)
return;
String inputFolderPath = folder.getAbsolutePath() + File.separator;
model.setRootFolder(inputFolderPath);
System.out.print(inputFolderPath);
}
}
and in the FileListController this means you can now access the model directly in the initialize() method:
public class FileListController {
private final Model model;
#FXML
Label label_rootFolder;
public FileListController(Model model) {
this.model = model ;
}
public void initialize() {
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
Now your application class needs to create a controller factory that invokes these constructors. This is the tricky part: you probably want to use some reflection here and implement logic of the form: "if the controller class has a constructor taking a model, invoke it with the (shared) model instance; otherwise invoke the default constructor". This looks like:
public class NestedGUI extends Application {
Model model = new Model();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = null;
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getClassLoader().getResource("Main.fxml"));
fxmlLoader.setControllerFactory((Class<?> type) -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0] == Model.class) {
return c.newInstance(model);
}
}
// default behavior: invoke no-arg constructor:
return type.newInstance();
} catch (Exception exc) {
throw new RuntimeException(exc);
}
});
root = (BorderPane) fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
At this point you are basically one step along the way to creating a dependency injection framework (you are injecting the model into the controllers using a factory class...)! So you might just consider using one instead of creating one from scratch. afterburner.fx is a popular dependency-injection framework for JavaFX, and the core of the implementation is essentially the ideas in the code above.
An option I prefer is to use a custom component, instead of the <fx:include fx:id="analysisTab" source="FileListTab.fxml" />. So in Main.fxml, replace the <fx:include> line with:
<FileList fx:id="fileList"></FileList>
FileList is our new, custom component. You must also add <?import yourpackage.*?> to the top of Main.fxml, to make the classes of yourpackage available to FXML. (Obviously, yourpackage is the package containing all the classes and files in this question.)
Add below is the class for the custom component in yourpackage.FileList.java; mostly your code from the FileListController + the code required to load the FXML. Note however that it extends a JavaFX component, the VBox, making it a FXML component itself. The VBox was the root component in your FileListTab.fxml and must also be declared in the type attribute of the FileList.fxml below.
package yourpackage;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class FileList extends VBox {
private Model model; // NOT REALLY NEEDED! KEEPING IT BECAUSE YOUR FileListController HAD IT TOO...
#FXML
Label label_rootFolder;
public FileList() {
java.net.URL url = getClass().getResource("/yourpackage/FileList.fxml");
FXMLLoader fxmlLoader = new FXMLLoader(url);
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
}
catch( IOException e ) {
throw new RuntimeException(e);
}
}
public void setModel(Model model) {
label_rootFolder.textProperty().unbind();
this.model = model; // NOT REALLY NEEDED!
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
And the FileList.fxml. This is your own FileListTab.fxml, having its root VBox node replaced by the fx:root and the type="javafx.scene.layout.VBox" attribute, keeping all other attributes the same:
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2"
type="javafx.scene.layout.VBox"
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="15.0"
>
<children>
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Root folder:" />
<Label fx:id="label_rootFolder" />
</children>
</HBox>
<TextArea prefHeight="200.0" prefWidth="200.0" />
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Found files:" />
<Label fx:id="label_filesFound" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</fx:root>
Did you notice the fx:id in the <FileList> component above? You can now inject it in the MainController:
#FXML
private FileList fileList;
And propagate the setModel() call:
// in MainController
public void setModel(Model model) {
this.model = model;
this.fileList.setModel(model);
}

JavaFx custom components with Spring

I'm currently trying to create custom components in JavaFx for my desktop app, and I would like to use Spring DI.
I have read about how to make extendable components and without Spring I can do it, based on this code https://github.com/matrak/javafx-extend-fxml.
But when I replace the FXMLLoader with a custom SpringFXMLLoader it can't load the components.
In the main window, there is a button and a editbox, and on click I woul'd like to create a new custom module, with the given name. But it says, that the label in the custom component is null.
What am I doing wrong?
BasicModule.class
#org.springframework.stereotype.Controller
#DefaultProperty(value = "extension")
public class BasicModule extends BorderPane {
public Label label;
public AnchorPane extension;
public BasicModule() {
super();
}
public ObservableList<Node> getExtension() {
return extension.getChildren();
}
}
BasicModule.fxml
<fx:root type="javafx.scene.layout.BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.akos.fxmlTest.BasicModule">
<top>
<Label fx:id="label" text="Label Basic" BorderPane.alignment="CENTER"/>
</top>
<center>
<AnchorPane fx:id="extension" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"/>
</center>
</fx:root>
CustomModule.class
public class CustomModule extends BasicModule {
#FXML
public Label customModuleLabel;
public void setText(String text) {
customModuleLabel.setText(text);
}
}
CustomModule.fxml
<fx:root type="com.akos.fxmlTest.BasicModule" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.akos.fxmlTest.CustomModule">
<Label fx:id="customModuleLabel" text="asdasdasdasd"/>
</fx:root>
Main.class
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
context.getBeanFactory().registerResolvableDependency(Stage.class, primaryStage);
SpringFxmlLoader loader = context.getBean(SpringFxmlLoader.class);
Parent parent = (Parent) loader.load("/fxml/main.fxml");
primaryStage.setScene(new Scene(parent, 1000, 600));
primaryStage.setTitle("test");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
main.fxml
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.akos.fxmlTest.Controller">
<center>
<VBox fx:id="itemList" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets left="50.0" right="50.0" />
</BorderPane.margin>
</VBox>
</center>
<left>
<VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="addButton" mnemonicParsing="false" onAction="#addNewModule" text="Button" />
<TextField fx:id="moduleNameEdit" />
</children>
</VBox>
</left>
</BorderPane>
Controller.class
#org.springframework.stereotype.Controller
public class Controller {
public VBox itemList;
public Button addButton;
public TextField moduleNameEdit;
public void addNewModule(ActionEvent actionEvent) {
CustomModule customModule = new CustomModule();
itemList.getChildren().add(customModule);
customModule.setText(moduleNameEdit.getText());
}
}
AppConfiguration.class
#Configuration
#ComponentScan("com.akos.fxmlTest")
public class AppConfiguration {}
Sorry for the wall of text.

How do I make my VBox and Label resize according to texts in label in JavaFx?

As you can see, my texts are constrained by labels. How do I make the label resize to exactly fit the text and also to resize the VBox and GridPane accordingly. I only want to resize vertically.
My FXML:
<VBox fx:id="body"
alignment="TOP_CENTER"
prefHeight="150"
GridPane.vgrow="SOMETIMES"
prefWidth="300"
GridPane.columnIndex="0"
GridPane.rowIndex="1" spacing="5">
<Label alignment="CENTER" textAlignment="JUSTIFY" fx:id="word" maxWidth="268"/>
<Pane prefHeight="10" VBox.vgrow="NEVER"/>
<Label alignment="CENTER" textAlignment="JUSTIFY" wrapText="true" fx:id="meaning" maxWidth="268" />
<Label alignment="CENTER" textAlignment="JUSTIFY" wrapText="true" fx:id="sentence" maxWidth="268" />
</VBox>
This VBox is inside the GridPane:
<GridPane fx:id="base"
alignment="CENTER"
prefHeight="210.0"
maxWidth="310.0"
stylesheets="#style.css"
vgap="0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.Controller">
...
Minimal, Complete, and Verifiable example as requested by #James_D
Main Class:
public class Main extends Application {
private Stage stage;
private StackPane stackPane;
#Override
public void start(Stage primaryStage) throws Exception{
stage = primaryStage;
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = (Parent) loader.load();
stackPane = new StackPane(root);
Scene scene = new Scene(stackPane);
scene.setFill(Color.WHITE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller Class:
public class Controller implements Initializable{
#FXML private Label label1;
#FXML private Button changeLabel;
private StringProperty labelString = new SimpleStringProperty();
#Override
public void initialize(URL location, ResourceBundle resources) {
labelString.setValue("aasdasd sad asdasd asdasda");
label1.textProperty().bind(labelString);
}
#FXML
public void clicked(MouseEvent e){
labelString.setValue("asdsadasd asdasdasd sfdgsfwoef fgtrhfgbdrgdf dfgdfivbjkfd gdfgidfjvdf gdfgjldkvbdf gjdilgjdfv dfgojdflkgdf ");
}
}
sample.fxml:
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10" maxHeight="Infinity">
<children>
<VBox fx:id="body"
alignment="CENTER"
maxHeight="Infinity"
GridPane.vgrow="SOMETIMES"
prefWidth="300"
GridPane.columnIndex="0"
GridPane.rowIndex="1" spacing="5">
<Label alignment="CENTER" VBox.vgrow="ALWAYS" wrapText="true" textAlignment="CENTER" fx:id="label1" maxWidth="268"/>
<Button fx:id="changeLabel" text="Change" minWidth="50" onMouseClicked="#clicked" maxWidth="Infinity" />
</VBox>
</children>
Expectation:
When I press 'Change' button, Everything should resize to just give enough space for text to be shown.
Problem:
When I press 'Change' button, UI remains same not showing the full text.
Assuming you want the text to wrap, the labels will need to be able to grow vertically. Add the attribute
maxHeight="Infinity"
to each label. You are also constraining the overall height of the VBox with prefHeight="150"; remove that attribute and let the VBox figure out its own height. Similarly, you probably want to remove the prefHeight attribute from the GridPane.

SubScene in JavaFX does not show elements loaded from an FXML file

I have a program created with an FXML file (made in SceneBuilder) that has four SubScenes in it:
#FXML
public SubScene p1sub;
#FXML
public SubScene p2sub;
#FXML
public SubScene p3sub;
#FXML
public SubScene p4sub;
Each of these subscenes is nearly identical to the rest.
I can get the root node (which contains these) to show up just fine, but when I try to add the SubScenes, they don't show up.
//This is the code I use to initialize one of the four.
Parent root2a = null;
try {
FXMLLoader loader2 = new FXMLLoader(getClass().getResource(
"PlayerConfigurationSubScreen.fxml"));
root2a = (Parent) loader2.load();
} catch (Exception e) {
/*code*/
}
/*code*/
if (root2a != null) {
System.out.println("root2 isn't null");
p1sub = new SubScene(root2a, 149, 260);
}
/*code*/
stage.show();
Any idea how to make them show up? I'm new at JavaFX.
Set the subscene dimensions in fxml and instead of "new" just add the parent to the subscene.
....
p1sub.setRoot(root2a);
....
I think you are approaching the problem the wrong way. From what I understand from your code, you want to load a subscene and then assign it to one of your predefined "slots".
This however will not work, as the pre-defined slots has nothing to do with what is actually rendered. Everything that is drawn in a window has to be assigned as child of a container of some kind.
I've made an example here that reproduces the problem you are facing:
public class ReplaceTest extends Application {
#FXML Button button;
#FXML Label oldLabel;
#Override
public void start(Stage primaryStage) throws IOException{
Parent root = FXMLLoader.load( getClass().getResource("ReplaceTest.fxml") );
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private static int i = 0;
#FXML protected void simpleClick(ActionEvent e){
Label newLabel = new Label("Hello! " + i++);
oldLabel = newLabel;
System.out.println("Nothing happens...");
}
}
And the FXML file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="200.0" spacing="10.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="replaceTest.ReplaceTest">
<children>
<Label fx:id="oldLabel" text="TEXT TO BE REPLACED" />
<Button fx:id="button" onAction="#simpleClick" text="Replace label" />
</children>
<padding>
<Insets top="10.0" />
</padding>
</VBox>
If you try this example, you will see that overwriting oldLabel does not affect the rendering window. Instead, you can try to create areas to which you add your new subscenes as children. This will update the rendering window and will (hopefully) produce the behavior you desire. Again, I've made an example you can consult:
public class ReplaceTest extends Application {
#FXML Button button;
#FXML VBox wrappingBox;
#Override
public void start(Stage primaryStage) throws IOException{
Parent root = FXMLLoader.load( getClass().getResource("ReplaceTest.fxml") );
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private static int i = 0;
#FXML protected void simpleClick(ActionEvent e){
Label newLabel = new Label("Hello! " + i++);
wrappingBox.getChildren().clear();
wrappingBox.getChildren().add(newLabel);
System.out.println("Someting happens!");
}
}
And the FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="200.0" spacing="10.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="replaceTest.ReplaceTest">
<children>
<VBox fx:id="wrappingBox" alignment="TOP_CENTER">
<children>
<Label text="TEXT TO BE REPLACED" />
</children>
</VBox>
<Button fx:id="button" onAction="#simpleClick" text="Replace label" />
</children>
<padding>
<Insets top="10.0" />
</padding>
</VBox>

Categories

Resources