I am parsing an XML file and populating the javafx fields with the XML values.
However, these fields are in a tab & I want to clone the tab & its content based on the node count from the XML.
Below is the fxml file screenshot:
I know one method of doing it is have the tab content in a separate FXML file and include it, but the problem of doing so is I have to populate the fields with data & I won't be able to populate data if load same fxml file multiple times with duplicate fx:ids.
Any method by which the above can be achieved?
Here is an example read the comments and you could probably do you fxml in a tab and load it into the tab pane if you want to save yourself a line of code
Main Class
public class Main extends Application {
#Override
public void start(Stage stage) {
TabPane tabPane = new TabPane();
ArrayList<Controller> controllerArrayList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//Don't just load it into the new node save a reference
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample.fxml"));
try {
//Load it into the new parent node
Tab tab = new Tab("Tab:"+i, loader.load());
//Save contoller to arraylist of controllers
controllerArrayList.add(loader.getController());
//Add to tabPane
tabPane.getTabs().add(tab);
} catch (IOException e) {
e.printStackTrace();
}
}
//Do some stuff with your contollers
int index = 0;
for (Controller controller : controllerArrayList) {
controller.setLabel("index:"+index);
controller.setTextField("index:"+index++);
}
Scene scene = new Scene(tabPane);
stage = new Stage();
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Controller Class
public class Controller{
public TextField textField;
public Label label;
public void setTextField(String text){ textField.setText(text); }
public void setLabel(String text){ label.setText(text); }
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
<children>
<Label fx:id="label"/>
<TextField fx:id="textField" />
</children>
</VBox>
Related
Specifically, I have a Primary Stage and two other Stages initialized from the Primary Stage when a menu item is selected.
All three Stages contain TableViews that display different views of my data, and allow relevant actions. When the user is working in one of these Stages and performs an action that changes the data, I would like the changes to be reflected in all three TableViews.
Each of the TableViews is backed by an ObservableArrayList. They update automatically when an element is added or removed, but I have to call the TableView.refresh() method anytime the data changes in any other way and I want it to show.
From reading other posts it seems that it is possible to pass a reference of a Parent Controller object to a Child controller, but it is not considered good practice. It occurred to me that perhaps I could create a new class that would be responsible for refreshing the tables in all 3 Stages, however that would require obtaining a reference to each of the controller objects somehow.
I'm stuck and I'd be grateful for any suggestions!
In attempting to create a minimal reproducible example I figured out what I was doing wrong:
In my original code I was converting Simple Double Properties to Simple String Properties before displaying them in the table, in order to control how they were displayed. The conversion was executed in the overwritten Call() method of Column.setCellValueFactory(). Somehow this conversion was causing the table not to respond to data changes right away.
Here is some code to illustrate what I am talking about:
public class Controller {
#FXML
public TableView<Person> mainTable;
#FXML
public Button editButton;
#FXML
public BorderPane mainBorderPane;
public Button openSecondButton;
public Button refreshButton;
public void initialize(){
DataModel.getInstance().addPerson(new Person("Frank", 1, 20));
DataModel.getInstance().addPerson(new Person("Cindy", 2, 20));
DataModel.getInstance().addPerson(new Person("Eric", 3, 67));
mainTable.setItems(DataModel.getInstance().getPeople());
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>(){
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> c){
return c.getValue().nameProperty();
}
});
TableColumn<Person, Integer> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
#Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().idProperty().asObject();
}
});
TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
#Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().ageProperty().asObject();
}
});
TableColumn<Person, String> ageStringColumn = new TableColumn<>("Age String");
ageStringColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> person) {
return new SimpleStringProperty(String.valueOf(person.getValue().getAge()));
}
});
mainTable.getColumns().addAll(nameColumn, idColumn, ageColumn, ageStringColumn);
}
#FXML
private void showSecondStage(ActionEvent actionEvent) throws IOException {
Stage secondStage = new Stage();
secondStage.setTitle("Secondary Stage");
secondStage.initModality(Modality.NONE);
secondStage.initStyle(StageStyle.UTILITY);
Parent parent = FXMLLoader.load(getClass().getResource("secondary.fxml"));
secondStage.setScene(new Scene(parent));
secondStage.initOwner(mainBorderPane.getScene().getWindow());
secondStage.show();
}
public boolean handleEditPersonRequest() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(mainBorderPane.getScene().getWindow());
dialog.setTitle("Edit Person");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(Controller.class.getResource("dialog.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
DialogController controller = fxmlLoader.getController();
controller.setFields(mainTable.getSelectionModel().getSelectedItem());
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.addEventFilter(ActionEvent.ACTION, event -> {
if (!controller.validateAndProcess()) {
event.consume();
System.out.println("Invalid entry, try again");
}});
Optional<ButtonType> result = dialog.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
}
public void refreshTable(ActionEvent actionEvent) {
mainTable.refresh();
}
}
And the .fxml file
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:id="mainBorderPane" fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" >
<left>
<VBox>
<Button text="Edit Person" fx:id="editButton" onAction="#handleEditPersonRequest"/>
<Button text = "Open Second Window" fx:id="openSecondButton" onAction="#showSecondStage"/>
<Button text="Refresh table" fx:id="refreshButton" onAction="#refreshTable"/>
</VBox>
</left>
<center>
<TableView fx:id="mainTable" />
</center>
</BorderPane>
Here is the dialog controller:
public class DialogController {
public TextField nameField;
public TextField idField;
public TextField ageField;
public Person person;
public void setFields(Person selectedPerson) {
person = selectedPerson;
nameField.setText(person.getName());
idField.setText(String.valueOf(person.getId()));
ageField.setText(String.valueOf(person.getAge()));
}
public boolean validateAndProcess(){
try{
String name = nameField.getText();
int id = Integer.parseInt(idField.getText());
int age = Integer.parseInt(ageField.getText());
person.setName(name);
person.setId(id);
person.setAge(age);
return true;
}catch (NumberFormatException | NullPointerException e){
e.printStackTrace();
return false;
}
}
}
And it's .fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.DialogController"
prefHeight="400.0" prefWidth="600.0">
<Label text="Name"/>
<TextField fx:id="nameField"/>
<Label text="Id"/>
<TextField fx:id="idField"/>
<Label text="Age"/>
<TextField fx:id="ageField"/>
</VBox>
I'm not going to include the code for the second window, as it's not needed to see the problem.
I tried adding a Medusa Gauge to my JavaFX project that uses FXML.
I mapped the fx:id correctly in the scene builder and set a value to be displayed.
But unfortunately the value doesn't get displayed and only the default 0.00 is displayed.
Here is my code
Class that contains the main method - GaugeFX.java
public class GaugeFX extends Application {
#Override
public void start(Stage stage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("Demo.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (IOException ex) {
Logger.getLogger(GaugeFX.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
FXML - Demo.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import eu.hansolo.medusa.Gauge?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.102" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gaugeDemoFX.DemoController">
<children>
<Gauge fx:id="gaugeExample" alert="true" alertMessage="Almost full" animated="true" areaTextVisible="true" layoutX="123.0" layoutY="113.0" lcdFont="ELEKTRA" skinType="SLIM" />
</children>
</AnchorPane>
FXML Controller - DemoController.java
public class DemoController implements Initializable {
#FXML
Gauge gaugeExample;
#Override
public void initialize(URL url, ResourceBundle rb) {
GaugeBuilder builder = GaugeBuilder.create();
gaugeExample = builder.decimals(0).maxValue(10000).unit("Questions").build();
gaugeExample.setValue(45);
}
}
I tried looking at the documentation. There the examples were done in hard coding. I noticed that the Gauge's value was set before showing the stage.
https://community.oracle.com/docs/DOC-992746
But according to my knowledge i have done the same even if i used FXML to make the project.
Can someone please tell me where i went wrong that the value i set doesn't get displayed?
You're overwriting value of the field containing the Gauge created in the fxml with a new one in the initialize method. The new one is never added to the scene; you only see the old unmodified one.
If Gauge works the same way JavaFX standard Controls work, you only need to use setters instead of using the builder:
#Override
public void initialize(URL url, ResourceBundle rb) {
gaugeExample.setDecimals(0);
gaugeExample.setMaxValue(10000);
gaugeExample.setUnit("Questions");
gaugeExample.setValue(45);
}
Furthermore it should also be possible to assign the properties from the fxml file:
<Gauge fx:id="gaugeExample"
alert="true"
alertMessage="Almost full"
animated="true"
areaTextVisible="true"
layoutX="123.0"
layoutY="113.0"
lcdFont="ELEKTRA"
skinType="SLIM"
decimals="0"
maxValue="10000"
unit="Questions"
value="45" />
I am looking to update text in a textfield based on some value. In order to make this sample simpler I have made my program smaller. The problem seems to be when I put top.setText("This is my new Text");
I looked at this:
how to change the text of TextField in java fx 2
but the answer does not seem to make sense. I don't know why you'd initialize a textfield that has already been implemented. Regardless it did not work.
I have also looked at:
NullPointerException (JavaFX Label.setText())
This seems to be the closest to what I think is the issue, but when I did the following I get an error. Just for clarity this is in the JavaFXApplication5 class.
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("FXML.fxml")
);
FXMLLoader.setController(this); // non-static method setController(Object)
// can not be referenced from static context ERROR****
Parent root = (Parent) loader.load();
/*Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
*/
}
From reading on the internet I wondered if there was a race condition: http://gotoanswer.stanford.edu/nullpointerexception_in_javafx_initialize_method-8807679/
So I tried:
Platform.runLater(new Runnable() {
#Override public void run() {
top.setText("This is my new Text");
}
});
But that did not work. I know that it can be set in Scene Builder but I need a way to dynamically change it based on values from another class. I can figure out how to do that part if I can just figure out how to set it to begin with. Hopefully this explains enough to get some help.
FXMLController Class:
public class FXMLController implements Initializable {
#FXML private TextField top;
public FXMLController() {
System.out.println("Hi");
top.setText("This is my new Text"); // This breaks the program *********
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
FXML.fxml class:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="javafxapplication5.FXMLController">
<children>
<TextField fx:id="top" layoutX="171.0" layoutY="68.0" />
</children>
</AnchorPane>
JavaFXApplication5 class: // main class
public class JavaFXApplication5 extends Application {
#Override
public void start(Stage stage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
}
catch (Exception ex) {
Logger.getLogger(JavaFXApplication5.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can't use a constructor on your controller class (FXMLController), since it will be initilized by the FXMLLoader.
And you are right, the first link has a wrong answer, since the textfield will be initialized (because of the #FXML annotation) in this process.
So for starters, you can add some text to the textfield inside initialize, as it will be loaded from the beginning by the loader, and top will be already instantiated.
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my first Text");
}
}
Try this first, with your posted version of JavaFXApplication5, and check that works.
There are many ways to set the content on the field, but if you need to modify the text field from another class, just add a public method for that:
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my new Text");
}
public void setTopText(String text) {
// set text from another class
top.setText(text);
}
}
As an example, you could get an instance of your controller in your main class, and use it to pass the content to the text field, after the stage is shown. This will override the previous content.
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXML.fxml"));
Parent root = loader.load();
FXMLController controller = (FXMLController)loader.getController();
Scene scene = new Scene(root);
stage.setTitle("Java code");
stage.setScene(scene);
stage.show();
controller.setTopText("New Text");
}
I want to use two ChoiceBoxes offering the same select items with the exception of the one selected by the other. However, after selecting options a few times, the entire java process becomes unresponsive.
Update: the issue does not occur if I use ComboBox instead of ChoiceBox. However, an explanation for why it happens would be interesting.
I have two ChoiceBoxes
#FXML private ChoiceBox<String> firstCB;
#FXML private ChoiceBox<String> secondCB;
that initially have the same selection options
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
and event listeners to remove the new selection from the other ChoiceBox's options and make the old option available again
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
the equivalent Swing code with JComboBoxes and the following event handler works
firstCB.addItemListener(itemEvent -> {
if (itemEvent.getStateChange() == ItemEvent.DESELECTED) {
secondCB.addItem((String) itemEvent.getItem());
} else if (itemEvent.getStateChange() == ItemEvent.SELECTED) {
secondCB.removeItem(itemEvent.getItem());
}
});
Full code:
class test.Main
public class Main extends Application {
private Parent rootPane;
#Override
public void start(Stage arg0) throws Exception {
Scene scene = new Scene(rootPane);
arg0.setScene(scene);
arg0.show();
}
#Override
public void init() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
rootPane = loader.load();
loader.<View>getController().init();
}
public static void main(String[] args) {
launch(args);
}
}
class test.View
public class View {
#FXML
private ChoiceBox<String> firstCB;
#FXML
private ChoiceBox<String> secondCB;
public void init() {
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]firstCB selection changed%n", Thread.currentThread().getName());
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
// removing one of the event listeners doesn't help
secondCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]secondCB selection changed%n", Thread.currentThread().getName());
firstCB.getItems().remove(newVal);
firstCB.getItems().add(oldVal);
});
}
}
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<Pane fx:id="rootPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="90.0" prefWidth="180.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.View">
<children>
<ChoiceBox fx:id="firstCB" layoutX="14.0" layoutY="14.0" prefWidth="150.0" />
<ChoiceBox fx:id="secondCB" layoutX="14.0" layoutY="52.0" prefWidth="150.0" />
</children>
</Pane>
try this, it works fine for me
firstComboBox.valueProperty().addListener((obs, oldV, newV) -> {
secondComboBox.getItems().remove(newV);
if(oldV!= null) secondComboBox.getItems().add(oldV);
});
secondComboBox.valueProperty().addListener((obs, oldV, newV) -> {
firstComboBox.getItems().remove(newV);
if(oldV!= null) firstComboBox.getItems().add(oldV);
});
or other not best solution with observable and filtered list
public class Controller implements Initializable{
public ComboBox<String> firstComboBox;
public ComboBox<String> secondComboBox;
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<String> list = FXCollections.observableArrayList();
list.addAll("one", "two", "three", "four", "five");
firstComboBox.getItems().addAll(list);
secondComboBox.getItems().addAll(list);
firstComboBox.valueProperty().addListener(c -> refreshList(list, secondComboBox, firstComboBox));
secondComboBox.valueProperty().addListener(c -> refreshList(list, firstComboBox, secondComboBox));
}
private void refreshList(ObservableList<String> list, ComboBox<String> choiceBox, ComboBox<String> choiceBox2) {
String tmp = choiceBox.getValue();
FilteredList<String> filteredList = new FilteredList<>(list, string -> !string.equals(choiceBox2.getValue()));
choiceBox.setItems(filteredList);
choiceBox.setValue(tmp);
}
I want to make a customize list view in javafx. Here I need to bind multiple component in list cell as follow, like
one label, one textfield, one button under one HBox and
two button, one hyperlink, one label in another HBox and
these HBox comes under one VBox and
this VBox comes under the single list cell and
it will repeat and make a list View.
The code is
<ListView fx:id="ListView" layoutX="0" layoutY="30" prefWidth="600" prefHeight="300">
<HBox fx:id="listBox" alignment="CENTER_LEFT">
<padding><Insets top="5" bottom="5" left="5"></Insets> </padding>
<HBox alignment="CENTER_LEFT" prefWidth="170" minWidth="88">
<Label fx:id="surveyName" text="Field A" styleClass="Name"></Label>
</HBox>
<VBox styleClass="Description" prefWidth="155" minWidth="86">
<HBox>
<HBox styleClass="surveyDesIcon" prefWidth="20" prefHeight="16"></HBox>
<Label fx:id="surveyCode" text="PRW3456HJ"></Label>
</HBox>
<HBox>
<HBox styleClass="DateIcon" prefWidth="20" prefHeight="16"></HBox>
<Label fx:id="Date" text="PRW3456HJ"></Label>
</HBox>
</VBox>
<HBox fx:id="Status" prefWidth="160" minWidth="80">
<Label fx:id="StatusLabel" text="Checking Files.."/>
</HBox>
<HBox fx:id="StatusIcon1" prefWidth="50" prefHeight="50" alignment="CENTER">
<Label styleClass="StatusIcon1" prefWidth="24" prefHeight="24" alignment="CENTER"/>
</HBox>
<HBox fx:id="StatusIcon2" prefWidth="50" prefHeight="50" styleClass="StatusIconBox" alignment="CENTER">
<Hyperlink styleClass="StatusIcon2" prefWidth="24" maxHeight="24" alignment="CENTER"/>
</HBox>
</HBox>
</ListView>
I understand your question. There are mainly two ways to set items in a Listview:
1. Create the ObservableList and set the items of the ListView with the ObservableList (listView.setItems(observableList)).
2. Use the setCellFactory() method of the ListView class.
You would prefer to use the setCellFactory() method, because this approach simplies the process as well as it helps to separate out the business logic and the UI (FXML).
Here is a more detailed explaination:
1. Create a new FXML file with the name listview.fxml to contain the ListView, and set the ListViewController class as its controller:
File: listview.fxml:
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.ListView?>
<?import demo.ListViewController?>
<GridPane xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ListView fx:id="listView"/>
</GridPane>
2. Create the controller and name it ListViewController.
The controller can load the listview.fxml file and access the listview.
File: ListViewController.java:
package demo;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.util.Callback;
import java.io.IOException;
import java.util.Set;
public class ListViewController
{
#FXML
private ListView listView;
private Set<String> stringSet;
ObservableList observableList = FXCollections.observableArrayList();
public ListViewController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listview.fxml"));
fxmlLoader.setController(this);
try
{
Parent parent = (Parent)fxmlLoader.load();
Scene scene = new Scene(parent, 400.0 ,500.0);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setListView()
{
stringSet.add("String 1");
stringSet.add("String 2");
stringSet.add("String 3");
stringSet.add("String 4");
observableList.setAll(stringSet);
listView.setItems(observableList);
listView.setCellFactory(new Callback<ListView<String>, javafx.scene.control.ListCell<String>>()
{
#Override
public ListCell<String> call(ListView<String> listView)
{
return new ListViewCell();
}
});
}
}
3. First you need to set the value of the ObservableList. This is very important.
Then, set the items of list using the ObservableList and call the setCellFactory() method on the ListView. In the given example I just take the String values an add them to the String set (the Set<String> stringSet).
4. When the setCellFactory() method is called on the ListView, it will return the ListCell. So for sake of simplicity, I added a class which extends the ListCell, and the setGraphic() method is present for the ListCell() and will set the items of the ListCell.
File: ListViewCell.java:
package demo;
import javafx.scene.control.ListCell;
public class ListViewCell extends ListCell<String>
{
#Override
public void updateItem(String string, boolean empty)
{
super.updateItem(string,empty);
if(string != null)
{
Data data = new Data();
data.setInfo(string);
setGraphic(data.getBox());
}
}
}
5. I just added a class which will load the listCellItem.fxml and return the HBox, which will contain the other components as children.
The HBox is then set to the ListCell.
File: listCellItem.fxml:
<?import demo.Data?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<HBox xmlns:fx="http://javafx.com/fxml" fx:id="hBox">
<children>
<Label fx:id="label1"/>
<Label fx:id="label2"/>
</children>
</HBox>
File: Data.java:
package demo;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import java.io.IOException;
public class Data
{
#FXML
private HBox hBox;
#FXML
private Label label1;
#FXML
private Label label2;
public Data()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listCellItem.fxml"));
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setInfo(String string)
{
label1.setText(string);
label2.setText(string);
}
public HBox getBox()
{
return hBox;
}
}
Using this way, you can use the setCellFactory() method to separate the things that are business logic and FXML.
Hope this is helpful.
The example above by #Anvay needs a couple of tweaks to work. These are simple things to set on-track.
The ListViewController needs to be running on the JavaFX application thread.
You can only call the injected #FXML elements from the JavaFX initialize() method
Need to call setListView()
The stringSet in the example needs to be allocated with a new before calling setListView().
The ListViewController below works with these changes. I changed "stringSet" to a list, "stringList". The controller is pretty much the sample controller provided by Scene Builder 2
public class ListViewController
{
#FXML private ResourceBundle resources;
#FXML private URL location;
#FXML private ListView listView;
private List<String> stringList = new ArrayList<>(5);
private ObservableList observableList = FXCollections.observableArrayList();
public void setListView(){
stringList.add("String 1");
stringList.add("String 2");
stringList.add("String 3");
stringList.add("String 4");
observableList.setAll(stringList);
listView.setItems(observableList);
listView.setCellFactory(
new Callback<ListView<String>, javafx.scene.control.ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> listView) {
return new ListViewCell();
}
});
}
#FXML
void initialize() {
assert listView != null : "fx:id=\"listView\" was not injected: check your FXML file 'CustomList.fxml'.";
setListView();
}
}//ListViewController
The JavaFX platform needs to be started in the main() method from a JavaFX Application. Netbeans conviently provides most of this structure from the Maven JavaFX application template.
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/CustomList.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
stage.setTitle("CustomList");
stage.setScene(scene);
stage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
*
* #param args the command line arguments
**/
public static void main(String[] args) {
launch(args);
}
}
The answer by Anvay for some reason didnt work for me, what i had to do to fix it was just some very small tweaks:
remove import data statement from listCellItem.fxml
as the comment below the post states in Data.java put hBox = fmxlLoader.load()
I also had a main class (intellij auto generated).
public class MainMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("MainController.fxml"));
try
{
Parent root = fxmlLoader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Title");
primaryStage.show();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
launch(args);
}
I know this was probably obvious for most of the experts here, but these issues perplexed me for hours while i was debugging.