Concatenate Javafx fx:Id - javafx

I'm kinda new to JavaFX and currently trying to do a Calendar application for a school project. I was wondering if there was a way to concatenate a fx:id such a
#FXML
private Label Box01;
(In function)
String ExampleNum = "01";
(Box+ExampleNum).setText("Test");

In addition to the methods mentioned by #jewelsea here are 2 more ways to do this:
Create & inject a Map containing the boxes as values from the fxml:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="a"/>
<Label text="bar" fx:id="b"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
<fx:define>
<HashMap fx:id="boxes">
<box1>
<fx:reference source="a"/>
</box1>
<box2>
<fx:reference source="b"/>
</box2>
</HashMap>
</fx:define>
</children>
</VBox>
Controller
public class Controller {
private Map<String, Label> boxes;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
boxes.get("box"+number.getValue()).setText("42");
}
}
Pass the namespace of the FXMLLoader, which is a Map<String, Object> mapping fx:ids to the associated Objects, to the controller:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="box1"/>
<Label text="bar" fx:id="box2"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
</children>
</VBox>
Controller
public class Controller implements NamespaceReceiver {
private Map<String, Object> namespace;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
((Label)namespace.get("box" + number.getValue())).setText("42");
}
#Override
public void setNamespace(Map<String, Object> namespace) {
this.namespace = namespace;
}
}
public interface NamespaceReceiver {
public void setNamespace(Map<String, Object> namespace);
}
Code for loading the fxml:
public static <T> T load(URL url) throws IOException {
FXMLLoader loader = new FXMLLoader(url);
T result = loader.load();
Object controller = loader.getController();
if (controller instanceof NamespaceReceiver) {
((NamespaceReceiver) controller).setNamespace(loader.getNamespace());
}
return result;
}

Various possible solutions:
You could use reflection, but that would be ugly and I wouldn't advise it.
Normally, if you have a lot of things, you put them in a collection like a list or array. The label will be a child of some layout pane, so you can get the children of the pane and lookup an item by index with something like:
((Label) parent.getChildren().get(0)).setText("Text");
If the label has been assigned a css id then you can use that to lookup the label.
For example, in your FXML define:
<Label text="hello" fx:id="Box01" id="Box01"/>
Then you can lookup the label using:
String boxNum = "01";
Label box = (Label) parent.lookup("#Box" + boxNum);
Just refer to the item by it's reference:
#FXML private Label box01;
box01.setText("Test");
Aside: Please use camel case as per standard Java conventions.

Related

Update component in javafx

Hy guys, I'm developing an application using javafx and fxml.
What I'm currently trying to do is to update a flowpane, adding components to it when the user performs an action (is not the case but let's take as an example the click of a button).
Let's suppose that the method just has to add buttons with the passed labels to the flowpane.
In the Controller of the fxml that contains the flowpane I have this:
public class CategoryPaneController implements Initializable {
#FXML
private FlowPane categoryContainer;
public void setCategories(String[] labels) throws IOException{
for(String label : labels){
Button button = new Button(label);
categoryContainer.getChildren().add(button);
}
}
}
This method is called in another controller as follows:
public class AddCategoryController implements Initializable {
#FXML
private Pane addCategoryPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void refreshCategories(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/categoryPane.fxml"));
loader.load();
CategoryPaneController cat = loader.getController();
String[] labels = {"categoria3", "categoria4"};
cat.setCategories(labels);
}
}
The categoryPane.fxml is the following:
<ScrollPane fx:id="storeMainPane" fx:controller="controller.CategoryPaneController">
<content>
<VBox prefHeight="629.0" prefWidth="862.0">
<children>
<Label alignment="TOP_CENTER" contentDisplay="CENTER" prefHeight="68.0" prefWidth="857.0" text="Magazzino" />
<FlowPane fx:id="categoryContainer" prefHeight="549.0" prefWidth="862.0" />
</children>
</VBox>
</content>
</ScrollPane>
And the following is the addCategory.fxml file
<Pane fx:id="addCategoryPane" fx:controller="controller.AddCategoryController">
<children>
<Button onAction="#refreshCategories" text="aggiungi categoria" />
</children>
</Pane>
I've debbugged the code and the method is called in the right way, it adds the buttons to the flowpane but the latter doesn't change.
Thanks in advance. <3
Every time you load a new FXML a new Controller instance and new nodes will be generated.
If you want to use an existing controller and nodes, you need to store a reference to the originally loaded controller and invoke methods on that.
Calling methods on a controller.
Or you can use MVC:
Applying MVC With JavaFx

JavaFX FXML include fxml causes NullPointerException

I want to extract a button to a new fxml file and change the main label with it. Without extraction it works perfectly.
main.fxml:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<Button onAction="#changeLabel" text="sayHello" />
</VBox>
MainController:
public class MainController {
#FXML
private Label label;
#FXML
private void changeLabel() {
label.setText("Changed");
}
}
With extraction I get NullPointerException in MainController.changeLabel()
main.fxml with include:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
button.fxml:
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Button onAction="#changeLabel" text="sayHello" />
</AnchorPane>
What can cause this NPE?
You should (almost?) always use a different class for controllers for different FXML files. (The only exception I can think of is if you want to define different FXML files to represent different layouts for the same controls.)
One approach is to inject the controller for the included FXML (the "Nested Controller") into the main controller. (See documentation.)
public class MainController {
#FXML
private Label label;
#FXML
private ButtonController buttonController ;
#FXML
private void initialize() {
buttonController.setOnButtonPressed(this::changeLabel);
}
private void changeLabel() {
label.setText("Changed");
}
}
public class ButtonController {
private Runnable onButtonPressed ;
public void setOnButtonPressed(Runnable onButtonPressed) {
this.onButtonPressed = onButtonPressed ;
}
public Runnable getOnButtonPressed() {
return onButtonPressed ;
}
#FXML
private void changeLabel() {
if (onButtonPressed != null) {
onButtonPressed.run();
}
}
}
And then the FXML files look like
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include fx:id="button" source="button.fxml"/>
</VBox>
and
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ButtonController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
Generally speaking, it's a bad idea for controllers to have references to each other, as it breaks encapsulation and adds unnecessary dependencies. A better approach is to use a MVC design.
public class Model {
private final StringProperty text = new SimpleStringProperty() ;
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
Now you can do
public class MainController {
#FXML
private Label label;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
#FXML
private void initialize() {
label.textProperty().bind(model.textProperty());
}
}
and
public class ButtonController {
private final Model model ;
public ButtonController(Model model) {
this.model = model ;
}
#FXML
private void changeLabel() {
model.setText("Changed");
}
}
The FXML files are as above, and you need to specify a controller factory when you load the FXML (so that the controllers are instantiated by passing the model instance to the constructors):
final Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/main.fxml");
loader.setControllerFactory(type -> {
if (type.equals(MainController.class)) return new MainController(model);
if (type.equals(ButtonController.class)) return new ButtonController(model);
throw new IllegalArgumentException("Unexpected controller type: "+type);
});
Parent root = loader.load();
// ...

NullPointerException passing a string from a window to another

Long story short, I searched for similar problems to mine, so I could troubleshoot this "by my own". I found these examples here and here, but none of them worked for me.
I need to pass to the next window a string that was typed in the TextField of the first window.
My main:
public class Main extends Application{
/**
* #param stage
* #throws java.lang.Exception
*/
#Override
public void start(Stage stage) throws Exception {
FXMLLoader floader = new FXMLLoader();
Parent root = FXMLLoader.load(getClass().getResource("java1.fxml"));
Java1Controller j1 = (Java1Controller)floader.getController();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
As I'm using FXML, here's the controller of Window1 (java1):
public class Java1Controller implements Initializable {
#FXML
private TextField tSend;
#FXML
private Button bSend;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void sendText(ActionEvent event) throws IOException {
FXMLLoader fLd = new FXMLLoader();
Parent novo = FXMLLoader.load(getClass().getResource("java2.fxml"));
Java2Controller j2 = (Java2Controller)fLd.getController();
j2.setLbText(tSend.getText());
Scene novScene = new Scene(novo);
Stage novStage = (Stage)((Node)(event.getSource())).getScene().getWindow();
novStage.setScene(novScene);
novStage.setResizable(false);
novStage.show();
}
}
And the controller of Window2 (java2):
public class Java2Controller implements Initializable {
#FXML
private Label lbText;
#FXML
private Button bOK;
/**
* Initializes the controller class.
* #param url
* #param rb
*/
/**
* #param string the string to set
*/
public void setLbText(String string) {
lbText.setText(string);
}
#FXML
private void closeJPane(ActionEvent event) {
Stage stage = (Stage)((Node)(event.getSource())).getScene().getWindow();
stage.close();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
The problem is pointed to the Java1Controller line where I setText with my custom function (j2.setLbText). Why? If it matters, I'm using Netbeans 8.2 and JavaFX Scene Builder 11.0.
FXML Java1:
<Pane maxHeight="252.0" maxWidth="388.0" minHeight="252.0" minWidth="388.0" prefHeight="252.0" prefWidth="388.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.Java1Controller">
<children>
<Label layoutX="126.0" layoutY="76.0" text="Insira texto aqui:">
<font>
<Font size="16.0" />
</font>
</Label>
<TextField fx:id="tSend" layoutX="109.0" layoutY="102.0" />
<Button fx:id="bSend" layoutX="169.0" layoutY="178.0" mnemonicParsing="false" onAction="#sendText" text="Send" />
</children>
</Pane>
FXML Java2:
<Pane maxHeight="388.0" maxWidth="252.0" minHeight="252.0" minWidth="388.0" prefHeight="252.0" prefWidth="388.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.Java2Controller">
<children>
<Label fx:id="lbText" layoutX="137.0" layoutY="106.0" text="Texto enviado">
<font>
<Font size="16.0" />
</font>
</Label>
<Button fx:id="bOK" layoutX="164.0" layoutY="178.0" mnemonicParsing="false" onAction="#closeJPane" prefHeight="26.0" prefWidth="61.0" text="OK" />
</children>
</Pane>
You're using static load methods to load the fxmls. This way a new FXMLLoader instance is created by the method that you cannot get a reference to. The FXMLLoader instance you create yourself is never used to load an fxml file, so no controller is available via its getController property:
// here you create a loader without setting a location
FXMLLoader fLd = new FXMLLoader();
// here a static method uses another loader instance to load the fxml
Parent novo = FXMLLoader.load(getClass().getResource("java2.fxml"));
// here you try to retrieve the controller from the loader instance that is still in the same state as after the constructor completion
Java2Controller j2 = (Java2Controller)fLd.getController();
Change your code an use the loader instance you created yourself to load the fxml:
// create loader with location set
FXMLLoader fLd = new FXMLLoader(getClass().getResource("java2.fxml"));
// use loader instance to load fxml from location specified
Parent novo = fLd.load();
// the controller should now be available
Java2Controller j2 = (Java2Controller)fLd.getController();
assert j2 != null

JavaFX FXML Text Component Controller not updating

I am having a difficult time getting an FXML Text component to update after being dynamically created using FXMLLoader. This supposedly simple program asks a user to input the number of die to roll, creates new dice based upon this input, rolls them with a random number generator, sums these values, and provides a button to re-roll any of the die if they want. Each die is added to the main window (using FXMLoader and a custom die component) showing its index, the value of the roll, and a button to push if you want to re-roll a particular die. The values of the die present and update great. The re-roll buttons work great. However, for some reason, the index for each die does not show in the window - not even initially. The Text component in question (the one that won't show any values) is identified as fx:id="number" in the custom FXML document and as #FXML private Text number in the custom controller.
I'm assuming the issue has to do with the Text component's getters and setters and I'm lucky that the Text component named value works. However, if this is the case, the proper names have alluded me as I have checked many alternatives involving Property in the name, etc.
Note: I understand the sum of the die values won't be updated when the re-roll buttons are pushed. I only care about the die values being updated for now.
The main FXML window for the program is:
<GridPane fx:id="mainPane"
styleClass="mainFxmlClass"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.lgdor.dieSim.MainWindowController"
xmlns="http://javafx.com/javafx/8.0_40"
prefHeight="800.0"
prefWidth="800.0"
hgap="10"
vgap="10" >
<stylesheets>
<URL value="#mainwindow.css"/>
</stylesheets>
<padding>
<Insets top="25" right="25" bottom="10" left="25"/>
</padding>
<Text fx:id="welcomeText" text="Welcome to DieSim"
GridPane.columnIndex="0"
GridPane.rowIndex="0"
GridPane.columnSpan="5"/>
<Label fx:id="spinnerText"
text="Please input the number of dice you want to roll:"
GridPane.columnIndex="0"
GridPane.rowIndex="1"
GridPane.columnSpan="2"/>
<Spinner fx:id="spinner" editable="true"
GridPane.columnIndex="3"
GridPane.rowIndex="1" />
<HBox fx:id="buttonBox"
spacing="10"
alignment="center"
GridPane.columnIndex="5"
GridPane.rowIndex="1">
<Button text="Roll Dice" onAction="#createDice"/>
</HBox>
<Label fx:id="dieNumberText" text="The number of dice is: "
GridPane.columnIndex="0"
GridPane.rowIndex="2"
GridPane.columnSpan="2"/>
<Text fx:id="dieNumber" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<Label fx:id="dieSumText" text="The sum of the dice is: "
GridPane.columnIndex="0"
GridPane.rowIndex="3"
GridPane.columnSpan="2"/>
<Text fx:id="sumNumber" GridPane.columnIndex="3" GridPane.rowIndex="3" />
<VBox fx:id="rowBox"
spacing="10"
alignment="center"
GridPane.columnIndex="0"
GridPane.rowIndex="4"
GridPane.columnSpan="5" >
<Text text="This is where the list of dice goes..."/>
</VBox>
The main controller for this is:
public class MainWindowController implements Initializable {
#FXML
Label spinnerText;
#FXML
Spinner spinner;
#FXML
HBox buttonBox;
#FXML
Text dieNumber;
#FXML
Text sumNumber;
#FXML
int numberOfDice = 0;
#FXML
int sumOfDice = 0;
#FXML
VBox rowBox;
private List<Die> dieList = new ArrayList<>();
private List<GridPane> rowList = new ArrayList<>();
public void createDice() throws IOException{
numberOfDice = (int)spinner.getValue();
for (int i=0;i<numberOfDice;i++){
Die die = new Die(i);
die.roll(null);
sumOfDice = sumOfDice + Integer.parseInt(die.getValue());
dieList.add(die);
rowBox.getChildren().add(die);
}
dieNumber.setText(String.valueOf(numberOfDice));
sumNumber.setText(String.valueOf(sumOfDice));
spinner.setVisible(false);
spinnerText.setVisible(false);
buttonBox.setVisible(false);
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
spinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 20));
dieNumber.setText("0");
sumNumber.setText(String.valueOf("0"));
spinnerText.setVisible(true);
spinner.setVisible(true);
buttonBox.setVisible(true);
}
}
The die custom component that is dynamically created in the for loop above is (problem component is fx:id="number"):
<fx:root type="javafx.scene.layout.GridPane"
xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/8.0_40" >
<HBox spacing="30" alignment="center" GridPane.columnIndex="0" GridPane.rowIndex="0" >
<Text fx:id="number" /><!--This is the problem component-->
<Text fx:id="value"/>
<Button fx:id="dieButton" text="Roll Dice" onAction="#roll" />
</HBox>
</fx:root>
and its dynamically created controller is:
public class Die extends GridPane implements Initializable {
#FXML
private Text number;//This is the problem component ...
public String getNumber() {
return numberTextProperty().get();
}
public void setNumber(String number) {
numberTextProperty().set(number);
}
public StringProperty numberTextProperty() {
return number.textProperty();
}
#FXML
private Text value;
public String getValue() {
return valueTextProperty().get();
}
public void setValue(String value) {
valueTextProperty().set(value);
}
public StringProperty valueTextProperty() {
return value.textProperty();
}
public Die(int i) throws IOException{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DieGUI.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
this.number = new Text(String.valueOf(i));
this.setNumber(String.valueOf(1));
this.value = new Text();
this.setValue(String.valueOf(1));
fxmlLoader.load();
}
#FXML
public void roll(ActionEvent event){
this.setValue(String.valueOf((int)(6*Math.random()+1)));
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
The problem is you create a new Text instance, instead of configuring the one already created in the FXML:
this.number = new Text(String.valueOf(i));
The whole purpose of an #FXML annotation is to indicate that the field is initialized by the FXMLLoader, so it is always a mistake to initialize fields annotated #FXML.
Note here that these fields are initialized by the FXMLLoader during the call to load(), so you need to call load() before you configure them in any way.
So your constructor should look like:
public Die(int i) throws IOException{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DieGUI.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
fxmlLoader.load();
// these don't seem to do anything useful?
// this.setNumber(String.valueOf(1));
// this.setValue(String.valueOf(1));
this.number.setText(String.valueOf(i));
this.value.setText(String.valueOf(i));
}

How can I create my own icon with propertise in JavaFX

Maybe somebody knows the answer and try help me.
I am creating own button.
<fx:root maxHeight="100.0" maxWidth="100.0" minHeight="50.0" minWidth="50.0" prefHeight="80.0" prefWidth="80.0" style="-fx-background-color: red;" type="StackPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" >
<children>
<ImageView fx:id="baseImage" fitHeight="66.0" fitWidth="72.0" pickOnBounds="true" preserveRatio="true" StackPane.alignment="TOP_CENTER" />
<Label fx:id="textBtn" alignment="BOTTOM_LEFT" prefHeight="17.0" prefWidth="75.0" textFill="WHITE" textOverrun="CLIP" StackPane.alignment="BOTTOM_LEFT" />
</children>
</fx:root>
So I need to change my button (Image and Label), when I am creating this in FXML file.
<MyButton layoutX="200.0" layoutY="162.0" />
e.g
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
Can somebody help me ?
My Java Code
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
init();
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public Label getTextBtn() {
return textBtn;
}
public void setTextBtn(Label textBtn) {
this.textBtn = textBtn;
}
public ImageView getBaseImage() {
return baseImage;
}
public void setBaseImage(Image location) {
this.baseImage.setImage(location);
}
public void setButton(Label textBtn, Image location){
this.baseImage.setImage(location);
this.textBtn = textBtn;
}
But I care about icon are changed in FXML file, not JavaCode
}
If you want to set properties in FXML:
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
you must define those properties in the corresponding class. In particular, MyButton must define setImage(...) and setText(...) methods (it already has setLayoutX(...) and setLayoutY(...) which are inherited from StackPane). It's hard to know exactly what functionality you want here, but you probably want to set these up as JavaFX Properties. If the intention is to map these into the Label and ImageView defined in the FXML file, you can just expose the relevant properties from the controls. You might need to work a bit to map the string from the image property into the correct thing.
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
// not sure what this is:
// init();
// note that if you define
// public void initialize() {...}
// it will be called
// automatically during the FXMLLoader.load() method
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public StringPropergty textProperty() {
return textBtn.textProperty();
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
// similarly expose a property for image, but you need to be able to coerce it from a String
}
(Incidentally, I assume this is just an example for the purposes of understanding how to do this. Everything you have in the example can be done using a regular button. Just wanted to make that clear for any others reading this post.)

Resources