It has been so difficult for me to try and get an answer for this. Oracle doesn't document on their website for some reason?! It has been asked on stackoverflow already (JavaFX: TableView(fxml) filling with data, and JavaFX FXML table; why won't my data display), but when i try to recreate with the answers given, I am still unable to get it to work. I would be extremely grateful if someone would be able to help! I'll give out all the code of have put together.
public class Heroes extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
AnchorPane page = FXMLLoader.load(Heroes.class.getResource("FXMLDocument.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Sample Window");
primaryStage.setResizable(false);
primaryStage.show();
} catch (Exception e) {
}
}
}
public class FXMLDocumentController implements Initializable {
/**
* Initializes the controller class.
*/
#FXML
TableView<Table> tableID;
#FXML
TableColumn<Table, Integer> tID;
#FXML
TableColumn<Table, String> tName;
#FXML
TableColumn<Table, String> tDate;
#FXML
TableColumn<Table, String> tPrice;
int iNumber = 1;
ObservableList<Table> data =
FXCollections.observableArrayList(
new Table(iNumber++, "Superman", "12/02/2013", "20"),
new Table(iNumber++, "Batman", "2/02/2013", "40"),
new Table(iNumber++, "Aquaman", "1/02/2013", "100"),
new Table(iNumber++, "The Flash", "12/02/2013", "16"));
#Override
public void initialize(URL url, ResourceBundle rb) {
// System.out.println("called");
tID.setCellValueFactory(new PropertyValueFactory<Table, Integer>("rID"));
tName.setCellValueFactory(new PropertyValueFactory<Table, String>("rName"));
tDate.setCellValueFactory(new PropertyValueFactory<Table, String>("rDate"));
tPrice.setCellValueFactory(new PropertyValueFactory<Table, String>("rPrice"));
tableID.setItems(data);
}
}
public class Table {
SimpleIntegerProperty rID;
SimpleStringProperty rName;
SimpleStringProperty rDate;
SimpleStringProperty rPrice;
public Table(int rID, String rName, String rDate, String rPrice) {
this.rID = new SimpleIntegerProperty(rID);
this.rName = new SimpleStringProperty(rName);
this.rDate = new SimpleStringProperty(rDate);
this.rPrice = new SimpleStringProperty(rPrice);
System.out.println(rID);
System.out.println(rName);
System.out.println(rDate);
System.out.println(rPrice);
}
public Integer getrID() {
return rID.get();
}
public void setrID(Integer v) {
this.rID.set(v);
}
public String getrDate() {
return rDate.get();
}
public void setrDate(String d) {
this.rDate.set(d);
}
public String getrName() {
return rName.get();
}
public void setrName(String n) {
this.rDate.set(n);
}
public String getrPrice() {
return rPrice.get();
}
public void setrPrice(String p) {
this.rPrice.set(p);
}
}
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="heroes.FXMLDocumentController">
<children>
<SplitPane dividerPositions="0.535175879396985" focusTraversable="true" orientation="VERTICAL" 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="100.0" prefWidth="160.0">
<children>
<TableView fx:id="tableID" prefHeight="210.0" prefWidth="598.0" tableMenuButtonVisible="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn minWidth="20.0" prefWidth="40.0" text="ID" fx:id="tID" >
<cellValueFactory>
<PropertyValueFactory property="tID" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="100.0" text="Date" fx:id="tDate" >
<cellValueFactory>
<PropertyValueFactory property="tDate" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="200.0" text="Name" fx:id="tName" >
<cellValueFactory>
<PropertyValueFactory property="tName" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Price" fx:id="tPrice" >
<cellValueFactory>
<PropertyValueFactory property="tPrice" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0" />
</items>
</SplitPane>
</children>
</AnchorPane>
The PropertyValueFactory tries to find your data values in two ways:
1) Looking for a method e.g. nameProperty where name is what you specify in the constructor, in your example e.g. "rID".
Thus you need to a methods named like this (name of the field + "Property"):
public IntegerProperty rIDProperty() {
return rID;
}
And similar for your other properties.
2) If these aren't present, it looks for methods named e.g. getName where Name is what you specifiy in the constructor with the first letter capitalized (following the Java Beans convention).
In your example you don't have 1) but you seem to have 2) - but you haven't capitalized the first letter of the property name in the getters and setters!
So change these to e.g.
public Integer getRID() {
return rID.get();
}
And similar.
You need either 1) or 2) but it would be good practice to have both.
Also note that the problem is not related to FXML in any way.
Related
All existing answers were using a class object to display multiple columns. Do I have to use a class? Can I just use a string array like C#'s ListViewItem? If I can, how?
For example, display "hello" in the first column and "world" in the second column.
public class HelloController {
#FXML
private TreeTableView mytree;
#FXML
private TreeTableColumn colFirst;
#FXML
private TreeTableColumn colSecond;
#FXML
void initialize()
{
TreeItem<String[]> item = new TreeItem<String[]>(new String[]{"hello", "world"});
colFirst.setCellValueFactory((CellDataFeatures<Object, String[]> p)
-> new ReadOnlyStringWrapper(p.getValue().toString()));
mytree.setRoot(item);
}
}
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.fx2.HelloController">
<TreeTableView fx:id="mytree" prefHeight="200.0" prefWidth="200.0">
<columns>
<TreeTableColumn id="colFirst" prefWidth="75.0" text="First" />
<TreeTableColumn id="colSecond" prefWidth="75.0" text="Second" />
</columns>
</TreeTableView>
</VBox>
Never use raw types: parameterize your types properly:
public class HelloController {
#FXML
private TreeTableView<String[]> mytree;
#FXML
private TreeTableColumn<String[], String> colFirst;
#FXML
private TreeTableColumn<String[], String> colSecond;
// ...
}
Then in the lambda expression, p is a TreeTableColumn.CellDataFeatures<String[], String>, so p.getValue() is a TreeItem<String[]> and p.getValue().getValue() is the String[] representing the row.
So you can do
#FXML
void initialize() {
TreeItem<String[]> item = new TreeItem<String[]>(new String[]{"hello", "world"});
colFirst.setCellValueFactory(p
-> new ReadOnlyStringWrapper(p.getValue().getValue()[0]));
mytree.setRoot(item);
}
I have a desktop app that contains:
Main Class: that load the first fxml file -> SideBar.fxml
SideBar.fxml: contains BorderPane -> at the left of it, i create 2 buttons:
- the fist button: load sample fxml file
- the second button: load secondFxml file
sample.fxml: contains a tableView and a Button
secondFxml.fxml: contains a label
Controller: a class that control sample.fxml -> load random double values to a tableView
the issue is :
when i press the button (fill Table) in Pane 1 : it load the data to the tableView, untill now everything is going well
when i switch to the second pane and i return to the first pane the center border pane is reloaded again so the data of the tableView disappeared
what i want is when i return to the first pane the the table view stay as it was first
i try to hide the borderpane center but it doesn't work for me
i screenShot the issue:
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("SideBar.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 700, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
SideBarController:
public class SideBarController implements Initializable {
#FXML BorderPane borderPane;
public void openPane1(ActionEvent event) throws Exception {
loadScene("Sample.fxml");
}
public void openPane2(ActionEvent event) throws Exception {
loadScene("secondFxml.fxml");
}
private void loadScene(String sc) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(sc));
borderPane.setCenter(root);
}
#Override
public void initialize(URL location, ResourceBundle resources) { }
}
Controller:
public class Controller implements Initializable {
double[][] data = new double[5][5];
Random random = new Random();
ObservableList<double[]> observableLists = FXCollections.observableArrayList();
#FXML
TableView<double []> tableView = new TableView<>(observableLists);
#FXML
public void fillTable(ActionEvent event) throws IOException {
//Random Values
for (int i = 0; i <data.length ; i++) {
for (int j = 0; j <data[0].length ; j++) {
data[i][j]= random.nextDouble();
}
}
//Add data to ObservableLists
for (int i = 0; i <data.length ; i++) {
observableLists.add(data[i]);
}
//Create Columns
for (int i = 0; i <data[0].length ; i++) {
TableColumn<double[], Double> column= null;
column = new TableColumn<>("column "+i);
int finalI = i;
column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue()[finalI]));
tableView.getColumns().add(column);
}
// Fill TableView
tableView.setItems(observableLists);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
SideBar.fxml
<BorderPane fx:id="borderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.SideBarController">
<left>
<VBox prefHeight="400.0" prefWidth="173.0" style="-fx-background-color: black;" BorderPane.alignment="CENTER">
<children>
<Button mnemonicParsing="false" onAction="#openPane1" prefHeight="25.0" prefWidth="177.0" style="-fx-background-color: blue; -fx-border-color: white;" text="Pane 1" textFill="WHITE">
<VBox.margin>
<Insets top="50.0" />
</VBox.margin>
<font>
<Font name="System Bold" size="17.0" />
</font>
</Button>
<Button mnemonicParsing="false" onAction="#openPane2" prefHeight="25.0" prefWidth="176.0" style="-fx-background-color: blue; -fx-border-color: white;" text="Pane 2" textFill="WHITE">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<font>
<Font name="System Bold" size="17.0" />
</font>
</Button>
</children>
</VBox>
</left>
<center>
<Pane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<children>
<Label layoutX="163.0" layoutY="152.0" prefHeight="68.0" prefWidth="131.0" text="Home">
<font>
<Font size="46.0" />
</font>
</Label>
</children>
</Pane>
</center>
</BorderPane>
Sample.fxml
<Pane prefHeight="395.0" prefWidth="597.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<TableView fx:id="tableView" layoutX="77.0" layoutY="47.0" prefHeight="266.0" prefWidth="461.0" />
<Button layoutX="257.0" layoutY="329.0" mnemonicParsing="false" onAction="#fillTable" text="fill Table" />
</children>
</Pane>
SecondFxml.fxml
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="232.0" layoutY="153.0" text="Pane 2">
<font>
<Font size="46.0" />
</font>
</Label>
</children>
</Pane>
Do not reload from fxml when button is clicked. Do it once in initialize:
public class SideBarController implements Initializable {
#FXML BorderPane borderPane;
private Parent sample, secondFxml;
public void openPane1(ActionEvent event) throws Exception {
borderPane.setCenter(sample);
}
public void openPane2(ActionEvent event) throws Exception {
borderPane.setCenter(secondFxml);
}
private Parent loadScene(String sc) throws IOException {
return FXMLLoader.load(getClass().getResource(sc));
}
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
sample = loadScene("Sample.fxml");
secondFxml = loadScene("secondFxml.fxml");
} catch (IOException ex) {
ex.printStackTrace();
};
}
}
I have a TableView in my fxml. Within the controller this table is mapped with the #FXML annotation.
#FXML
TableView<BankMovement> table;
I also set, in fxml document, for table element
onMouseReleased = "#handleRowSelect"
finally always in the controller I have the following method
#FXML
private void handleRowSelect(MouseEvent event){
Document row = table.getSelectionModel().getSelectedItem();
if (row == null) return;
if(row != temp){
temp = row;
lastClickTime = new Date();
} else if(row == temp) {
Date now = new Date();
long diff = now.getTime() - lastClickTime.getTime();
if (diff < 300){
System.out.println("Edit dialog");
System.out.print(row.getDescription());
} else {
lastClickTime = new Date();
System.out.print(row.getFile());
}
}
}
here instead is where to bind the data to table
#Override
public void initializeTable() {
table.setItems(subList());
colId.setCellValueFactory(new PropertyValueFactory<>("idDocument"));
colIdCategory.setCellValueFactory(d -> new SimpleStringProperty(d.getValue().getCategory().getName()));
colIdCategoryChild.setCellValueFactory(d -> new SimpleStringProperty(d.getValue().getCategoryChild().getName()));
colIdDocument.setCellFactory(getFileCellFactory());
colIdDocument.setCellValueFactory(new PropertyValueFactory<>("file"));
colIdDescription.setCellValueFactory(new PropertyValueFactory<>("description"));
colIdDate.setCellValueFactory(d -> new SimpleStringProperty(Utils.localTimeToItalianDate(d.getValue().getDocumentDate())));
}
Even if I click on the table I never enter the handleRowSelect method.
I have the impression that when I click on the row the click on the cell is managed and not on the row. As if the event were not propagated.
This is fxml
<AnchorPane minWidth="-Infinity" prefHeight="900.0" prefWidth="1510.0" styleClass="background-white" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MyController">
<children>
<VBox prefHeight="886.0" prefWidth="1465.0" style="-fx-min-width: 100%;" AnchorPane.bottomAnchor="4.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="330.0" AnchorPane.topAnchor="10.0">
<children>
<TitledPane animated="false" minHeight="-Infinity" minWidth="-Infinity" prefHeight="880.0" prefWidth="1465.0" text="%app.pane.document">
<content>
<VBox minWidth="-Infinity" prefHeight="815.0" prefWidth="1474.0">
<children>
<TableView onMouseReleased="#handleRowSelect" fx:id="table" minHeight="-Infinity" minWidth="-Infinity" prefHeight="746.0" prefWidth="1450.0">
.......
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</TableView>
<Pagination fx:id="pagination" prefHeight="73.0" prefWidth="1450.0" />
</children>
</VBox>
</content>
</TitledPane>
</children>
</VBox>
</children>
</AnchorPane>
How i can do button action for editing TableView. I need to put text from TextArea to table when i touch button. And if put System.out.println in inputToTable() it is work.
public class InputController {
public TextArea inputArea;
public Button inputButton;
private TableController tableController;
public void initialize() {
tableControllerInit();
}
public void inputToTable() {
if(inputArea.getText() != "") {
tableController.tableInfo.setItems(FXCollections.observableArrayList(new InputObject(inputArea.getText())));
}
}
private void tableControllerInit() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("table.fxml"));
fxmlLoader.load();
tableController = fxmlLoader.getController();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TableController {
#FXML TableView<InputObject> tableInfo;
#FXML TableColumn<InputObject, String> col1;
public void initialize() {
col1.setCellValueFactory(new PropertyValueFactory<>("text"));
}
}
public class Controller implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
public class InputObject {
String text;
public InputObject(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
<BorderPane fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<left>
<fx:include source="table.fxml"/>
</left>
<center>
<fx:include source="input.fxml"/>
</center>
</BorderPane>
<TableView fx:controller="sample.TableController" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:id="tableInfo" prefHeight="400.0" prefWidth="330.0">
<columns>
<TableColumn fx:id="col1" prefWidth="75.0" text="Output" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
<VBox fx:controller="sample.InputController" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<TextArea fx:id="inputArea" prefHeight="188.0" prefWidth="270.0" />
<Button fx:id="inputButton" onAction="#inputToTable" mnemonicParsing="false" text="Input">
<VBox.margin>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</VBox.margin>
</Button>
</children>
</VBox>
You load table.fxml twice: once via the fx:include in the main FXML file, and once in InputController, via the FXMLLoader you create in the tableControllerInit() method. Consequently, two instances of TableController are created, one associated with the first UI you load from table.fxml, and one associated with the second UI you load from table.fxml.
The UI you load via the fx:include is displayed in the VBox defined in the main FXML file. The UI you load with the FXMLLoader is never displayed (in fact, you never even keep a reference to it, you just call loader.load() and discard the result). When you try to update the table's items (do you really intend to replace all the existing items, by the way?), you refer to the second controller instance, which is associated with the UI which is never displayed. Consequently, you are updating a table that is not displayed, and you never see any results.
What you really need to do is share the same data between the two controllers associated with the two fx:includes. You can do this simply by injecting those two controllers into the main controller, as described in the "Nested Controllers" section in the documentation.
First, give the fx:include elements fx:id attributes:
<BorderPane fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<left>
<fx:include fx:id="table" source="table.fxml"/>
</left>
<center>
<fx:include fx:id="input" source="input.fxml"/>
</center>
</BorderPane>
Then you can inject the controllers into the main controller by creating fields with the word "Controller" appended to the fx:id. Create a single observable list, which will represent the list of items displayed in the table, and pass it to each controller:
public class Controller implements Initializable {
#FXML
private TableController tableController ;
#FXML
private InputController inputController ;
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<InputObject> items = FXCollections.observableArrayList();
tableController.setTableItems(items);
inputController.setTableItems(items);
}
}
Finally, just define the obvious methods in each of the other two controllers:
public class TableController {
#FXML
private TableView<InputObject> tableInfo;
#FXML
private TableColumn<InputObject, String> col1;
public void initialize() {
col1.setCellValueFactory(new PropertyValueFactory<>("text"));
}
public void setTableItems(ObservableList<InputObject> tableItems) {
tableInfo.setItems(tableItems);
}
}
Now the table is displaying the contents of the items list created in the main controller's initalize() method, and the InputController has a reference to the same list. So all you need to do is update that list in the InputController. I assume you just want to add items to the table (not replace them all):
public class InputController {
#FXML
private TextArea inputArea;
#FXML
private Button inputButton;
private ObservableList<InputObject> tableItems ;
public void setTableItems(ObservableList<InputObject> tableItems) {
this.tableItems = tableItems ;
}
public void inputToTable() {
if(! inputArea.getText().isEmpty()) {
tableItems.add(new InputObject(inputArea.getText()));
}
}
}
More generally, if you have more data to share among the different controllers, you would create one or more "model" classes and share a model instance with the controllers. Then you can observe the properties of the model and update them. See Applying MVC With JavaFx for a more comprehensive example.
I am trying to change the data of the StackedAreaChart dynamically. So I have created an fxml file and a controller for it.
fig.fxml
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="495.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.gollahalli.test.controller">
<children>
<StackedAreaChart fx:id="graph" layoutX="24.0" layoutY="95.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</StackedAreaChart>
<TextField fx:id="number" layoutX="57.0" layoutY="39.0" />
<Button fx:id="button" layoutX="330.0" layoutY="39.0" mnemonicParsing="false" text="Button" />
</children>
</AnchorPane>
Controller.java
public class controller {
#FXML
private Button button;
#FXML
private TextField number;
#FXML
private StackedAreaChart<Number, Number> graph;
public void initialize(){
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
button.setOnAction(event -> {
int number1 = Integer.parseInt(number.getText());
System.out.println(number1);
for (int i = 0; i < number1; i++) {
series.getData().addAll(new XYChart.Data(i, i));
}
graph.getData().add(series);
});
}
}
When I enter a number say 100, I am able to get the graph correctly, when I change the data say 101 and click on the button I get an error as Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Duplicate series added.
I do know that to overcome this I would have to use ObservableList, but I am not sure how to use it.
Now the question is, how should I change/refresh the data every time I click on the button?
The exception message is clear. To avoid it add series only once:
public void initialize(){
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
// add series only once at init
graph.getData().add(series);
button.setOnAction(event -> {
int number1 = Integer.parseInt(number.getText());
System.out.println(number1);
// clear current data
series.getData().clear();
// add new data
for (int i = 0; i < number1; i++) {
series.getData().add(new XYChart.Data(i, i));
}
});
}