FXML: Binding to controller - javafx

Is it possible to bind in FXML just like in xaml to the controller to a class varibale. What I am doing is:
FXML
<ComboBox fx:id="searchField"
HBox.hgrow="ALWAYS" editable="true" />
<GridPane hgap="5" vgap="5">
<Label text="Nom" />
<Label text="$selecteClient.name"
GridPane.columnIndex="1" />
<Label GridPane.rowIndex="1" text="tél" />
<Label text="$electedClient.phoneNumber"
GridPane.rowIndex="1" GridPane.columnIndex="1" />
<GridPane/>
Controler:
private final List<Client> clients = FXCollections.observableArrayList(ImportingDataService.importClients());
#FXML
private Client selectedClient;
#FXML
private ComboBox<Client> searchField;
#Override
public void initialize(URL location, ResourceBundle resources) {
// Set appDtat client id so it refreshes when client is changed
this.appState.clientViewClientIDProperty().addListener((obs, oldValue, newValue) -> {
selectedClient = ImportingDataService.importClient(newValue.longValue());
});
// Set up combo box
setUpComboBox();
}
private void setUpComboBox() {
searchField.getItems().addAll(clients);
UtilService.autoCompleteComboBoxPlus(searchField, (typedText, client) -> client.getName().contains(typedText));
// Handle selecting clients
searchField.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
selectedClient = ImportingDataService.importClient(newValue.getId());
});
}
My Client class is a class that has the String fields name and phoneNumber. and the ImportingDataService.importClient is for importing data from database, they work just fine (I placed breakpoints and checked that). The problem is that I don't know why the client Labels do not update when I change the selection of the ComboBox, while the selectedClient does change. What am I doing wrong?

There are multiple reasons for this:
A simple field is not observable.
You do not include controller in the expression binding.
There is a typo here $selecteClient.name and here $electedClient.phoneNumber.
The whole expression needs to be wrapped inside {} to be bound, not just set.
You could e.g. fix it like this:
Controller
private final ObjectProperty<Client> selectedClient = new SimpleObjectProperty<>(initialClient);
public final Client getSelectedClient() {
return this.selectedClient.get();
}
public final void setSelectedClient(Client value) {
this.selectedClient.set(value);
}
public final ObjectProperty<Client> selectedClientProperty() {
return this.selectedClient;
}
...
// selectedClient = ImportingDataService.importClient(newValue.getId());
setSelectedClient(ImportingDataService.importClient(newValue.getId()));
fxml
<ComboBox fx:id="searchField"
HBox.hgrow="ALWAYS" editable="true" />
<GridPane hgap="5" vgap="5">
<Label text="Nom" />
<Label text="${controller.selectedClient.name}"
GridPane.columnIndex="1" />
<Label GridPane.rowIndex="1" text="tél" />
<Label text="${controller.selectedClient.phoneNumber}"
GridPane.rowIndex="1" GridPane.columnIndex="1" />
<GridPane/>
Client
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
(or something similar)

Related

Updating a JavaFX status label

I have a JavaFX program using Model/View/Controller where I want a long running model to update a status label on the view. I found people suggesting using the Timeline class to do this. I implemented it expecting that every second, the status Label would update. However, only the final status displays. What am I doing wrong?
My controller looks like:
#FXML
private Button pullApplicantsButton;
#FXML
private Label statusLabel;
#FXML
private DatePicker orientationDate;
#FXML
private Spinner numberOfApplicants;
#FXML
private void pullApplicants() throws Exception {
SelectApplicantsModel selectApplicantsModel = new SelectApplicantsModel(orientationDate.getValue() , ( int ) numberOfApplicants.getValue() , this.statusLabel);
selectApplicantsModel.process();
}
my model looks like:
public SelectApplicantsModel(LocalDate nextOrientationDate, int numberOfApplicants , Label statusLabel ) throws FileNotFoundException {
this.nextOrientationDate = nextOrientationDate;
this.numberOfApplicants = numberOfApplicants;
this.statusLabel = statusLabel;
}
public void process() throws Exception {
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds( 1 ) , event -> {
statusLabel.setText( programStatus );
})
);
timeline.setCycleCount( Animation.INDEFINITE );
timeline.play();
programStatus = "starting";
changeSearchStringToIncludeOrientationDate(nextOrientationDate);
MemberClicks memberClicks = new MemberClicks();
programStatus = "retrieving profiles";
JsonArray applicantProfilesJsonArray = memberClicks.getProfiles(searchJsonArray);
programStatus = "converting profiles";
and the view looks like:
<Label text="Picks the next 30 applicants for the upcoming orientation. Applicants whose Memberclick's OrientationDate matches the next orientation date get priority, followed by those with the oldest normalized application date." wrapText="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="0" />
<Label text="Date of next orientation:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<DatePicker fx:id="orientationDate" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Number of applicants to pull:" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<Spinner fx:id="numberOfApplicants" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Button fx:id="pullApplicantsButton" mnemonicParsing="false" onAction="#pullApplicants" text="Pull Applicants" GridPane.columnIndex="0" GridPane.rowIndex="4" />
<Button fx:id="closeWindowButton" mnemonicParsing="false" onAction="#closeWindow" text="Close Window" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label fx:id="statusLabel" text="" wrapText="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="5" />
You can use a Task for your purpose which does all the work in a background thread so that the GUI thread will not be blocked. Here is a minimal example:
Controller Class:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
#FXML
private Label statusLabel;
#FXML
public void handleStartBtnClick() {
MyTask myTask = new MyTask();
statusLabel.textProperty().bind(myTask.messageProperty());
new Thread(myTask).start();
}
}
MyTask Class:
package sample;
import javafx.concurrent.Task;
public class MyTask extends Task<Void> {
#Override
protected Void call() throws Exception {
updateMessage("starting");
// while (...) {
// do something:
// changeSearchStringToIncludeOrientationDate(nextOrientationDate);
// MemberClicks memberClicks = new MemberClicks();
Thread.sleep(1000); // just for demonstration purpose
// Update the status:
updateMessage("retrieving profiles");
Thread.sleep(1000);
// Do next step:
// ...
updateMessage("converting profiles");
Thread.sleep(1000);
// } End of while loop
return null;
}
#Override
protected void succeeded() {
updateMessage("succeeded");
}
}
FXML File:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<Button onAction="#handleStartBtnClick" text="Start background task"/>
<Label fx:id="statusLabel" text="Status"/>
</children>
</VBox>

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));
}

StringProperty and Switch Statement Javafx

My brain is about to explode. I am pretty new to java, so far self-taught but I really want to get this code to work. I am also not the best at making sense of the java docs.
I am trying to create a gameloop where the user can freely traverse the switch statement by clicking the buttons.
However, previously, when my gameloop method was called, it ran the code with the current settings without letting me use the buttons to traverse the switch statement.
I think this was because my "choice" variable did not have an action listener attached to it (because a listener will make the program sit on the item, waiting for something to happen?). Unfortunately, my choice variable was a String variable and you cannot attach an actionlistener to a String variable.
Now I tried using the StringProperty class to create a string object to be able to attach an action listener to it.
But now I don't even know how to use StringProperty with switch statement and I just feel like I am on the wrong track.
Does it make sense what I am trying to accomplish?
Can anybody help me create a gameloop where the user can freely traverse the switch statement by clicking the buttons?
♥♥♥!
Here is my code:
Controller:
package sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
import static java.lang.System.out;
public class Controller implements Initializable {
public String question;
public StringProperty choice = new SimpleStringProperty(this, "choice", "");
// this code starts when the XML file is loaded.
#Override
public void initialize(URL location, ResourceBundle resources) {
question = "0";
gameloop();
} public String getChoice() {
return choice.get();
}
public StringProperty choiceProperty() {
return choice;
}
public void setChoice(String choice) {
this.choice.set(choice);
}
// public String choice = "";
public Controller() {
}
// Here are the buttons trying to add an actionlistener to the choice object.
// Maybe wrong path to go
#FXML
public void button1() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c1");});
}
#FXML
public void button2() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c2");});
out.println(choice); //think this prints out where it is stored in the memory
}
#FXML
public void button3() {
choiceProperty().addListener((v, oldValue, newValue) ->{setChoice("c3");});
out.println(choice);
}
//this block of code does not work because of the switch (choice) statement.
//Where "c1","c2","c3" have incompatible a type error.
public void gameloop() {
switch (question) {
case "0":
switch (choice) {
case "c1":
out.println("you chose 1");
question = "1";
break;
case "c2":
out.println("you chose 2");
break;
case "c3":
out.println("you chose 3");
break;
}
case "1":
switch (choice) {
case "c1":
out.println("you chose a");
question = "0";
break;
case "c2":
out.println("you chose b");
break;
case "c3":
out.println("you chose c");
break;
}
}
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<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="sample.Controller">
<children>
<BorderPane layoutX="163.0" layoutY="82.0" prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: black;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<GridPane BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="button1" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button1" onMouseClicked="#button1" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" />
<Button fx:id="button2" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button2" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="1" />
<Button fx:id="button3" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button3" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="2" />
</children>
</GridPane>
</bottom>
<center>
<TextArea fx:id="textArea" editable="false" prefHeight="200.0" prefWidth="200.0" promptText="..." style="-fx-border-color: grey;" wrapText="true" BorderPane.alignment="CENTER" />
</center>
<top>
<Label fx:id="label" text="Chapter 1" textFill="WHITE" BorderPane.alignment="CENTER" />
</top>
<left>
<ImageView fx:id="image1" fitHeight="75.0" fitWidth="75.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</left>
<right>
<ImageView fx:id="image2" fitHeight="75.0" fitWidth="75.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</right></BorderPane>
</children>
</AnchorPane>
Main:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 800, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Registering listeners in the button event handlers is nonsense. Using listeners to modify the property they listen to is even worse. Passing a property as argument for a switch will not even compile.
In this case the smallest modification to make this work would be registering a single listener in the initialize method, calling the gameloop method from this listener and using the value stored in the property as argument for the switch.
#Override
public void initialize(URL location, ResourceBundle resources) {
question = "0";
gameloop();
choiceProperty().addListener((observable, oldValue, newValue) -> gameloop());
}
...
#FXML
private void button1() {
setChoice("c1");
}
...
public void gameloop() {
switch (question) {
case "0":
switch (choice.get()) {
...
}
...
}
}
Note however you'll get a LOT of repeated code this way. Better come up with a data structure to store the game data instead of hardcoding everything.
E.g.
public class GameData {
private final ReadOnlyObjectWrapper<Question> question;
public ReadOnlyObjectProperty<Question> questionProperty() {
return question.getReadOnlyProperty();
}
public Question getQuestion() {
return question.get();
}
private final Map<Integer, Question> questions;
public Map<Integer, Question> getQuestions() {
return questions;
}
public GameData(int initialQuestion, Map<Integer, Question> questions) {
this.questions = questions;
this.question = new ReadOnlyObjectWrapper(questions.get(initialQuestion));
}
public void activateChoice(Choice choice) {
question.set(questions.get(choice.getNextQuestion()));
}
}
public class Question {
private final String text;
public String getText() {
return text;
}
public Question(String text, Choice... choices) {
this.text = text;
this.choices = Collections.unmodifiableList(new ArrayList(Arrays.asList(choices)));
}
private final List<Choice> choices;
public List<Choice> getChoices() {
return choices;
}
}
public class Choice {
private final String text;
private final int nextQuestion;
public Choice(int nextQuestion, String text) {
this.text = text;
this.nextQuestion = nextQuestion;
}
public String getText() {
return text;
}
public int getNextQuestion() {
return nextQuestion;
}
}
public class Controller {
private GameData gameData;
#FXML
private Button button1, button2, button3;
private Button[] buttons;
#FXML
private void initialize() {
buttons = new Button[]{button1, button2, button3};
}
public void setGameData(GameData gameData) {
this.gameData = gameData;
gameData.questionProperty().addListener((observable, oldValue, newValue) -> setQuestion(newValue));
setQuestion(gameData.getQuestion());
}
private void setQuestion(Question question) {
System.out.println(question.getText());
for (Button b : buttons) {
b.setVisible(false);
}
for (int i = 0, max = Math.min(buttons.length, question.getChoices().size()); i < max; i++) {
Button b = buttons[i];
Choice c = question.getChoices().get(i);
b.setUserData(c);
b.setText(c.getText());
b.setVisible(true);
}
}
#FXML
private void button(ActionEvent evt) {
Node source = (Node) evt.getSource();
Choice choice = (Choice) source.getUserData();
out.println("You've chosen " + choice.getText());
gameData.activateChoice(choice);
}
}
...
<Button fx:id="button1" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" />
<Button fx:id="button2" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="1" />
<Button fx:id="button3" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#button" style="-fx-background-color: black; -fx-border-color: grey;" text="Button" textFill="WHITE" GridPane.rowIndex="2" />
...
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
Map<Integer, Question> questions = new HashMap<>();
questions.put(0, new Question("0",
new Choice(1, "1"),
new Choice(0, "2"),
new Choice(0, "3")
));
questions.put(1, new Question("1",
new Choice(0, "a"),
new Choice(1, "b"),
new Choice(1, "c")
));
GameData data = new GameData(0, questions);
loader.<Controller>getController().setGameData(data);

Concatenate Javafx fx:Id

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.

JavaFX, why is TableColumn null?

As you will see I am a massive noob when it comes to Java and Javafx. I have spent a lot of time reading around (various forum posts, and tuts) and trying to figure out myself where I am getting this issue but it has come to the point for me to post for feedback from someone who knows their business.
When replying, please could you take the time to also explain why something isn't working and some general pointers? Here is what I have so far (my FXML and my two classes) any pointers would be fantastic!!
My FXML;
<Pane id="myScene" fx:id="myScene" 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="sample.TestController">
<children>
<TableView id="employeesTable" fx:id="employeesTable" layoutX="131.0" layoutY="64.0" prefHeight="200.0" prefWidth="360.0">
<columns>
<TableColumn id="colFirstName" fx:id="colFirstName" prefWidth="75.0" text="First Name" />
<TableColumn id="colLastName" fx:id="colLastName" prefWidth="75.0" text="Last Name" />
<TableColumn id="colEmail" fx:id="colEmail" prefWidth="75.0" text="email" />
</columns>
</TableView>
</children>
</Pane>
Now the Employee class I have;
public class Employee {
private StringProperty firstName;
private StringProperty lastName;
private StringProperty email;
public Employee(String a, String b, String c) {
this.firstName = new SimpleStringProperty(a);
this.lastName = new SimpleStringProperty(b);
this.email = new SimpleStringProperty(c);
}
public Employee() {
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public String getEmail() {
return email.get();
}
public StringProperty emailProperty() {
return email;
}
public void setEmail(String email) {
this.email.set(email);
}
}
And finally the class for my test controller is
public class TestController {
public Label login;
public TextField loginUserName;
public PasswordField loginPassword;
public TextField testOutput;
#FXML TableView<Employee> employeesTable;
#FXML TableColumn<Employee, String> colFirstName;
#FXML TableColumn<Employee, String> colLastName;
#FXML TableColumn<Employee, String> colEmail;
#FXML Pane myScene;
//public javafx.scene.control.TableView employeesTable;
private ObservableList<Employee> myData;
private MainController MainController;
public void loadEmployeeForm(ActionEvent actionEvent) throws IOException, SQLException, ClassNotFoundException {
myData = FXCollections.observableArrayList(DBCON.getEmployees());
System.out.println(myData.size());
Parent root = FXMLLoader.load(getClass().getResource("frmEmployees.fxml"));
Scene myScene = new Scene( root );
sample.MainController.setScene(myScene);
colFirstName.setCellValueFactory(new PropertyValueFactory<Employee, String>("firstName"));
colLastName.setCellValueFactory(new PropertyValueFactory<Employee, String>("lastName"));
colEmail.setCellValueFactory(new PropertyValueFactory<Employee, String>("email"));
employeesTable.setItems(null);
employeesTable.setItems(myData);
employeesTable.setVisible(true);
}
I get a null pointer exception when I go to set colFirstName to the property value factory which make me think I haven't initialized something somewhere but I am utterly clueless on how to go about adding that.
If I add in lines such as;
TableColumn colFirstName = new TableColumn("firstName");
for each of my columns and the tablename it works (ie it doesn't throw a load of error messages at me) that way but then I don't get any data loading into the tableview because I think that's me creating a new tableView not using the one generated from the FXML?
I have a feeling it will be very simple, but as I said I am a massive noob and any points would be much obliged.
Thanks
Mark
Update 1;
The method for load employee form is called from a button on myMain.fxml;
<GridPane alignment="CENTER" hgap="10" prefHeight="300" prefWidth="300" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"
fx:controller="sample.TestController" stylesheets="/sample/myFirst.css">
<children>
<Button onAction="#login" text="Login" GridPane.halignment="CENTER" GridPane.rowIndex="5" GridPane.valignment="CENTER" />
<Button text="GoEmployees" onAction="#loadEmployeeForm" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
<Label fx:id="login" GridPane.rowIndex="1" />
<Label text="UserName" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label text="Password" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<TextField fx:id="loginUserName" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<PasswordField fx:id="loginPassword" GridPane.rowIndex="2" GridPane.columnIndex="1" blendMode="OVERLAY" />
<TextField fx:id="testOutput" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="3" />
</children>
<columnConstraints>
<ColumnConstraints prefWidth="125.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints prefHeight="50.0" />
</rowConstraints>
<padding>
<Insets bottom="10.0" left="9.0" right="10.0" top="10.0" />
</padding>
</GridPane>
Is having my testController control two different FXMLs a problem/a no go?
When the FXMLLoader tries to load an fxml file, it will create new instance of the controller class defined with fx:controller in fxml file. Then it creates and maps the #FXML annotated fields with fx:id components in fxml file. Finally, it calls the controller's initialize() method. You can get the instantiated controller with fxmlloader.getController() after fxmlloader.load().
According to this basic work flow, the pitfall in your code is:
myMain.fxml's controller is TestController, but myMain.fxml does not contain TableColumns with fx:id colFirstName etc. So these fields are null, when the myMain.fxml has been loaded. As a result, there will be NPE in loadEmployeeForm() while trying to use these fields.
Move the TableView and TableColumns to frmEmployees.fxml's controller, and configure them (setCellValueFactory, initial data etc.) in this controller's initialize() method.
You never use your Employee(String, String, String) constructor. This constructor initializes the firstName, lastName, and email. Otherwise, your references will point to nothing.

Resources