Content from ObservableArrayList gets loaded (probably) but not shown - java

I have created a little program that is supposed to load data from a database and display it in a TableView element in a JavaFX application.
The problem is that when I start the application all I see is empty rows. The data of the different rows doesn't get displayed. But I am certain that they are handled somehow, because the number of empty rows shown matches the amount of rows loaded from the database.
Also my System.out.prints for debug prints out the correct data from the database, so I'm certain that the problem isn't within the DBLoader class.
I took most of it from this guide. What I've done so far is inspired by the first two parts of the tutorial.
Edit: And yes, I have added DatabaseOverviewControll.java as the controller class of the DatabaseOverview.fxml. And I have added itemNumberColumn, descriptionColumn and priceColumn as the fx:id of their respective TableColumn element.
My PrototypeApp class that extends application, and is the runnable class:
package prototype;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import prototype.util.DBLoader;
import javafx.collections.*;
import prototype.view.DatabaseOverviewController;
import prototype.model.Row;
public class PrototypeApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private ObservableList<Row> databaseData = FXCollections
.observableArrayList();
/**
* The constructor. Creates an dbLoader object and loads the database data
* from that dbLoader into the ObservableArrayList databaseData;
*/
public PrototypeApp() {
//DBLoader dbLoader = new DBLoader();
// databaseData = dbLoader.getData(); //This is used by me, but for the
// sake of testability I added some mock data instead.
// Some mock data for Stack Overflow users.
databaseData.add(new Row("abc", "efg", "hij"));
databaseData.add(new Row("abc2", "efg2", "hij2"));
databaseData.add(new Row("abc3", "efg3", "hij3"));
databaseData.add(new Row("abc4", "efg4", "hij4"));
// For debugging.
for (Row row : databaseData) {
System.out.println(row.getItemNumber() + "\t" + row.getDescription() + "\t" + row.getPrice());
}
}
public ObservableList<Row> getDatabaseData() {
return databaseData;
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Prototype");
initRootLayout();
showDatabaseOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PrototypeApp.class
.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Shows the database overview inside the root layout.
*/
public void showDatabaseOverview() {
try {
// Load database overview
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PrototypeApp.class
.getResource("view/DatabaseOverview.fxml"));
AnchorPane databaseOverview = (AnchorPane) loader.load();
// Set the database overview into the center of root layout.
rootLayout.setCenter(databaseOverview);
// Give the controller access to the prototype app.
DatabaseOverviewController controller = loader.getController();
controller.setPrototypeApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
*
* #return
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
And here's my DatabaseOverviewController class:
package prototype.view;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import prototype.PrototypeApp;
import prototype.model.Row;
public class DatabaseOverviewController {
#FXML
private TableView<Row> databaseTable;
#FXML
private TableColumn<Row, String> itemNumberColumn;
#FXML
private TableColumn<Row, String> descriptionColumn;
#FXML
private TableColumn<Row, String> priceColumn;
#FXML
private Label itemNumberLabel;
#FXML
private Label descriptionLabel;
#FXML
private Label priceLabel;
// Reference to the prototype application
private PrototypeApp prototypeApp;
/**
* The constructor The constructor is called before the initialize() method.
*/
public DatabaseOverviewController() {
}
/**
* Initializes a controller class. This method is automatically called after
* the fxml file has been loaded.
*/
#FXML
private void intialize() {
// Initialize the database table with the three columns.
itemNumberColumn.setCellValueFactory(cellData -> cellData.getValue()
.itemNumberProperty());
descriptionColumn.setCellValueFactory(cellData -> cellData.getValue()
.descriptionProperty());
priceColumn.setCellValueFactory(cellData -> cellData.getValue()
.priceProperty());
}
/**
* A method called by the prototype application to give a reference back to
* itself.
*
* #param prototypeApp
*/
public void setPrototypeApp(PrototypeApp prototypeApp) {
this.prototypeApp = prototypeApp;
// Adds observable list data to the table
databaseTable.setItems(prototypeApp.getDatabaseData());
}
}
And here's my row class that represents a row in the SQL table:
package prototype.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/*
* Model class for a SQL row.
*
* #author Jonatan Stenbacka
*/
public class Row {
private final StringProperty itemNumber;
private final StringProperty description;
private final StringProperty price;
/**
* Default constructor
*/
public Row() {
this(null, null, null);
}
/**
* Constructor
* #param itemNumber
* #param description
* #param price
*/
public Row (String itemNumber, String description, String price) {
this.itemNumber = new SimpleStringProperty(itemNumber);
this.description = new SimpleStringProperty(description);
this.price = new SimpleStringProperty(price);
}
public String getItemNumber() {
return itemNumber.get();
}
public void setItemNumber(String itemNumber) {
this.itemNumber.set(itemNumber);
}
public StringProperty itemNumberProperty() {
return itemNumber;
}
public String getDescription() {
return description.get();
}
public void setDescription(String description) {
this.description.set(description);
}
public StringProperty descriptionProperty() {
return description;
}
public String getPrice() {
return price.get();
}
public void setPrice(String price) {
this.price.set(price);
}
public StringProperty priceProperty() {
return price;
}
}
Edit: Here's my FXML files. Should be enough to run the program if you just add some mock Row data.
DatabaseOverview.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="prototype.view.DatabaseOverviewController">
<children>
<BorderPane layoutX="191.0" layoutY="58.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<Label text="Tables" BorderPane.alignment="CENTER">
<font>
<Font size="24.0" />
</font>
</Label>
</top>
<bottom>
<Button mnemonicParsing="false" onAction="#handleLoadDatabase" text="Load" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="10.0" />
</BorderPane.margin>
</Button>
</bottom>
<center>
<TableView fx:id="databaseTable" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn fx:id="itemNumberColumn" prefWidth="200.0" text="Item Number" />
<TableColumn fx:id="descriptionColumn" prefWidth="200.0" text="Description" />
<TableColumn fx:id="priceColumn" prefWidth="200.0" text="Price" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</center>
</BorderPane>
</children>
</AnchorPane>
RootLayout.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40">
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" text="Close" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Edit">
<items>
<MenuItem mnemonicParsing="false" text="Delete" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
<items>
<MenuItem mnemonicParsing="false" text="About" />
</items>
</Menu>
</menus>
</MenuBar>
</top>
</BorderPane>

There's a simple typo in the DatabaseOverviewController: you have intialize() as the method name, instead of initialize(). Since the FXMLLoader uses reflection to search for a method named initialize, and just does nothing if it doesn't find one, your method silently fails to be invoked. Fixing the typo fixes the problem.
For what it's worth, here's how I figured this out, as it's really hard to see. I observed, as you did, that the correct number of rows existed in the table (you can select them), but weren't displaying anything. This is almost always called by not having set the cellValueFactory (or having set it incorrectly, which can happen if you use a PropertyValueFactory but rarely happens when you use the nice approach with lambdas that you show here).
Since the initialize() method appeared to be setting these correctly, I added logging, first to Row.itemNumberProperty(), and then when I observed that method wasn't getting invoked (it should be invoked by each TableCell in that column), to the initialize() method itself. When I observed the initialize() method wasn't getting invoked, I found the typo.
Not sure what the general message is from this, if any? Perhaps it's better to use the old (pre JavaFX 2.1) style approach of having the controller implement Initializable, so that the compiler will catch mistyped method names. I suppose what would have been nicer is if the post-FXML-loading method to be invoked had a specific annotation (#PostLoading or something).

Related

How would one persist a single instance of a User model across multiple Controller with Java/JavaFX MVC?

Overview
I am working on a simple JavaFX application with multiple scenes using the MVC Architectural pattern. I have the design in place but I am having trouble persisting the User model across the different scenes. At the moment I do not have any backend. I am looking to just instantiate the User model when the application starts and display that single instance across my application. Any changes to that user model will up updated across the scenes. I am not looking to persist after the application closes.
I believe the issue arises from a design choice I had early on in development. I use a Controller named SceneNavigatorControl that calls the class Navigator which handles the presentation of different scenes in the application. These scenes are .fxml files.
SceneNavigatorControl
public class SceneNavigatorControl implements Initializable {
#FXML
private BorderPane mainStage;
#FXML
private void displayHomeScene() {
Navigator object = new Navigator();
Pane view = object.getScene("Home");
handleButtonChange("Home");
mainStage.setCenter(view);
}
#FXML
private void displayProfileScene() {
Navigator object = new Navigator();
Pane view = object.getScene("Profile");
handleButtonChange("Profile");
mainStage.setCenter(view);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// Called when the application starts, should this be where I instantiate User?
//User user = new User();
//user.setName("Bob");
displayHomeScene();
}
}
The above code handles which scene to take the user to. Depending on which button is tapped. One of the above functions is called and the scene is displayed by using the Navigator Class. I would think this is where I instantiate User and pass it to all other controllers
Navigator Class
public class Navigator {
private Pane view;
public Pane getScene(String fileName) {
try {
URL fileUrl = ActivityTracker.class.getResource("/ActivityTracker/Views/" + fileName + ".fxml");
if (fileUrl == null) {
throw new java.io.FileNotFoundException("FXML File cannot be found");
}
view = new FXMLLoader().load(fileUrl);
} catch (Exception e) {
System.out.print("No page " + fileName + " please check FXMLLoader");
}
return view;
}
}
The navigator class simple checks if a .fxml file with a given name exists. If it does, it replaces the current scene on the stage with the one given.
HomeView
<AnchorPane fx:id="Home" prefHeight="500.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ActivityTracker.Controllers.HomeController">
<children>
<Label fx:id="clockLabel" layoutX="90.0" layoutY="32.0" textFill="WHITE" AnchorPane.leftAnchor="90.0" AnchorPane.topAnchor="32.0">
<font>
<Font name="Lucida Grande" size="100.0" />
</font></Label>
<Pane layoutX="410.0" layoutY="-1.0" prefHeight="500.0" prefWidth="90.0" style="-fx-background-color: #1d1d1d;" />
</children>
This is our view which is created using an .fxml file. On the first line is where we declare what view is associated with what controller.
HomeController
public class HomeController implements Initializable {
#FXML
private Pane Home;
#Override
public void initialize(URL location, ResourceBundle resources) {
// Print users details
}
}
ActivityTracker (Main)
public class ActivityTracker extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(ActivityTracker.class.getResource("Views/Stage.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
} catch (Exception ex) {
Logger.getLogger(ActivityTracker.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
User Model
public class User {
// Define all user stat variables for the model
private String userName;
/**
* Method that returns the users name
* #return users name of type String
*/
public String getUsersName() {
return userName;
}
/**
* Method that takes in a string value and sets it as the current users name
* #param name the current users weight
*/
public void setUsersName(String name) {
this.userName = name;
}
}
The following is an mre of setting one model instance to two controllers.
Define a simple model to be used by the two controllers:
class User {
private final String fName, lName;
public User(String fName, String lName) {
this.fName = fName;
this.lName = lName;
}
public String getFirstName() {
return fName;
}
public String getLastName() {
return lName;
}
}
Define two very basic fxml views, each with a controller:
FirstName.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="fx_tests.FNameController">
<children>
<Label text="First Name: " HBox.hgrow="NEVER">
<font>
<Font size="16.0" />
</font>
</Label>
<Label fx:id="fName" text=""-"">
<font>
<Font size="16.0" />
</font>
</Label>
</children>
</HBox>
LastName.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="fx_tests.LNameController">
<children>
<Label text="Last Name: " HBox.hgrow="NEVER">
<font>
<Font size="16.0" />
</font>
</Label>
<Label fx:id="lName" text=""-"">
<font>
<Font size="16.0" />
</font>
</Label>
</children>
</HBox>
Define a simple interface:
interface Controller {
void setModel(User model);
}
And have the two controllers implement it:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class FNameController implements Controller{
#FXML
private Label fName;
#Override
public void setModel(User model) {
fName.setText(model.getFirstName());
}
}
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class LNameController implements Controller{
#FXML
private Label lName;
#Override
public void setModel(User model) {
lName.setText(model.getLastName());
}
}
Define the main view that is the parent of the FirstName.fxml and LastName.fxml:
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<BorderPane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fx_tests.MainController">
<top>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="User Info" textAlignment="CENTER" wrappingWidth="250" BorderPane.alignment="CENTER">
<font>
<Font size="20.0" />
</font>
</Text>
</top>
<center>
<VBox fx:id="infoPane" alignment="CENTER" spacing="10.0" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets left="20.0" />
</BorderPane.margin>
</VBox>
</center>
</BorderPane>
The controller of the main view sets the model to the two controllers:
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class MainController {
#FXML
private VBox infoPane;
#FXML
void initialize() throws IOException{
try {
User model = new User("Ann","Davis"); //initialize model
FXMLLoader loader = new FXMLLoader(getClass().getResource("FirstName.fxml"));
Pane fName = loader.load();
Controller controller = loader.getController();
controller.setModel(model); //set model to first name controller
loader = new FXMLLoader(getClass().getResource("LastName.fxml"));
Pane lName = loader.load();
controller = loader.getController();
controller.setModel(model); //set model to last name controller
infoPane.getChildren().addAll(fName, lName);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Test it all using:
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class FxmlTest extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Pane root = FXMLLoader.load(getClass().getResource("Main.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(null);
}
}

Pass/bind a collection to a custom component (extended from vbox) in FXML via parameters

In my application I have declared a custom component like this:
#DefaultProperty("todoItems")
public class TodoItemsVBox extends VBox {
private ObservableList<TodoItem> todoItems;
// Setter/Getter omitted
}
and now somewhere in the fxml I want to use the TodoItemsVBox component like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
stylesheets="#../css/app.css">
<top>
<HBox spacing="10.0">
<TextField fx:id="input" layoutX="35.0" layoutY="64.0" prefWidth="431.0" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button layoutX="216.0" layoutY="107.0" mnemonicParsing="false" onAction="#addTask" prefHeight="27.0" prefWidth="70.0" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${todoTasks}"/>
</ScrollPane>
</center>
... so as we can see the fxml has it's controller TodoListController
public class TodoListController implements {
private final ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList(/*Fill in the collection somehow - for now doesn't matter*/);
#FXML
private TodoItemsVBox todoItemsVBox;
// Setter/Getter omitted
}
So, here what I want to do: pass todoTasks into TodoItemsVBox defined in the FXML via such construction: todoItems="${todoTasks}" ---- unfortunately this doesn't work as I expected, because fxml files load before controllers initialised so todoTasks is always null. I also tried #NamedArg with one arg constructor in TodoItemsVBox - it even fails with exception: "Cannot bind to untyped object.”.
Could some one suggest a solution how to pass a collection of objects, defined in a controller, into a custom component via it's parameters?
There are two issues with the code as you have it:
For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to ObservableLists as well as regular values. So your TodoItemsVBox class needs to expose a ListProperty todoItemsProperty()
FXML expression bindings (i.e. ${todoTasks}) reference the FXMLLoader's namespace, not the controller. The controller is automatically injected into the namespace (with key "controller"), so, given that the task list is stored in your controller (which is not necessarily a good idea) you can use ${controller.todoTasks} here.
Here's a minimal, complete version of your app which works.
A basic TodoItem.java:
public class TodoItem {
private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
A TodoItemsVBox that exposes the list as a property:
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class TodoItemsVBox extends VBox {
private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();
public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}
private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}
public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}
public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}
}
A simple controller:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
#FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}
The FXML file (TodoList.fxml):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import org.jamesd.examples.TodoItemsVBox ?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>
<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>
And finally the application class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TodoApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Really, a controller is no place to be storing data; you should have a separate model class to do that, which is shared between the controller and view. This is reasonably straightforward to do here; you just need to do a little more work with the FXMLLoader (namely putting the model in the namespace, and manually creating and setting the controller).
For example:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class TodoModel {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}
Then your controller becomes:
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
private TodoModel model ;
public TodoModel getModel() {
return model;
}
public void setModel(TodoModel model) {
this.model = model;
}
#FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}
Modify the FXML to use
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>
And finally assemble the application with
public void start(Stage primaryStage) throws Exception {
TodoModel model = new TodoModel();
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());
TodoListController controller = loader.getController();
controller.setModel(model);
primaryStage.setScene(scene);
primaryStage.show();
}
The advantage to this approach is that your data are now separated from the UI (both view and controller), which becomes essential if you want to access the same data in another part of the UI (which would use another FXML and another controller).

JavaFX: TableView Cell pixelates ImageView - How do I undo transform?

Edit: This question has been completely reworked in light of new information.
This is using javafx8 and SceneBuilder2.
I have found that making a TableView the child of an accordion's AnchorPane, and then (in SceneBuilder) right clicking on the TableView, and clicking "fit-to-parent" causes images displayed in table cells to distort and lose quality; things look bold. I passed this image
to the table constructor to be displayed. The one on the left is before "fit-to-content" was applied; the one on the right was after.
Does anyone have some idea of a way I can get around this? Because I need a table in an accordion to dynamically resize horizontally with the window without my image losing quality.
Minimal Example Below
The controller first
package application;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import java.net.URL;
public class SampleController implements Initializable
{
#FXML private TableView<SomeTableItems> someTable;
#FXML private TableColumn<SomeTableItems, String> imagesRow;
#FXML private TableColumn<SomeTableItems, String> description;
#FXML private TableColumn<SomeTableItems, String> modified;
#FXML private TableColumn<SomeTableItems, String> created;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
imagesRow.setCellValueFactory(new PropertyValueFactory<SomeTableItems, String>("imagesRow"));
description.setCellValueFactory(new PropertyValueFactory<SomeTableItems, String>("description"));
modified.setCellValueFactory(new PropertyValueFactory<SomeTableItems, String>("modified"));
created.setCellValueFactory(new PropertyValueFactory<SomeTableItems, String>("created"));
someTable.setItems(getSomeTableItems());
}
private ObservableList<SomeTableItems> getSomeTableItems()
{
ObservableList<SomeTableItems> itemsToReturn = FXCollections.observableArrayList();
itemsToReturn.add(new SomeTableItems("/imageAssets/tableImage1.png","secondContent", "thirdContent", "fourthContent"));
return itemsToReturn;
}
}
Assume you have a package called imageAssets containing an image titled "tableImage1.png" for the above code. The constructor for SomeTableItems is below.
package application;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class SomeTableItems
{
private Node imagesRow;
private SimpleStringProperty description;
private SimpleStringProperty modified;
private SimpleStringProperty created;
public Node getImagesRow()
{
return imagesRow;
}
public String getDescription()
{
return description.get();
}
public String getModified() {
return modified.get();
}
public String getCreated()
{
return created.get();
}
public void setImagesRow(String pathToImage)
{
this.imagesRow = new ImageView(new Image(getClass().getResourceAsStream(pathToImage)));
}
public void setDescription(SimpleStringProperty description)
{
this.description = description;
}
public void setModified(SimpleStringProperty modified)
{
this.modified = modified;
}
public void setCreated(SimpleStringProperty created)
{
this.created = created;
}
public SomeTableItems()
{
this.imagesRow = new ImageView(new Image(getClass().getResourceAsStream("/imageAssets/tableImage1.png")));
this.description = new SimpleStringProperty("-");
this.modified = new SimpleStringProperty("-");
this.created = new SimpleStringProperty("-");
}
public SomeTableItems(String pathToImage, String description, String modified, String created)
{
this.imagesRow = new ImageView(new Image(getClass().getResourceAsStream(pathToImage)));
this.description = new SimpleStringProperty(description);
this.modified = new SimpleStringProperty(modified);
this.created = new SimpleStringProperty(created);
}
}
Below is the fxml made in SceneBuilder2
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane prefHeight="206.0" prefWidth="384.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController">
<center>
<Accordion BorderPane.alignment="CENTER">
<panes>
<TitledPane animated="false" text="untitled 3">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<TableView fx:id="someTable" prefHeight="180.0" prefWidth="382.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn fx:id="imagesRow" prefWidth="75.0" text="C1" />
<TableColumn fx:id="description" prefWidth="75.0" text="C2" />
<TableColumn fx:id="modified" prefWidth="75.0" text="C3" />
<TableColumn fx:id="created" prefWidth="75.0" text="C4" />
</columns>
</TableView>
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</center>
</BorderPane>
The above code reproduces the error shown in the screenshot at the beginning. If you look at the line in the fxml where the TableView is declared, you can undo the fit-to-parent effect by replacing the line with the below code. It is the difference between the bolding "glitch" happening or not.
<TableView fx:id="someTable" prefHeight="200.0" prefWidth="402.0" BorderPane.alignment="CENTER">
And the Main is generic. I will happily include a main upon request though.
Thanks to anyone interested and willing to assist me. I really appreciate your time.
There is almost never a good reason to use AnchorPane. While it is possible for AnchorPane to have legitimate uses, in practice it is far more common that AnchorPane is used the same way null layouts were used in AWT/Swing: to exactly specify the location and size of a node.
Forcing the size of your table is what’s causing the image to get (slightly) resized.
The easiest solution is to use a layout which will respect the preferred size of its child node, such as a BorderPane or StackPane:
<BorderPane>
<center>
<TableView fx:id="someTable" …
…
</TableView>
</center>
</BorderPane>

Close tab in another Controller - JavaFX

I'm designing a simple email client in Java. I'm using two FXML file as the view, and of course, 2 Java controller, each one for the right view.
My goal is to close the WriteView tab clicking on the button "delete" in the Writeview, but I have some problems, because I created the new tab in which I've load the WriteView in the MainVIewController, and now i don't have any id or kind of reference to close the tab unless the "x" button beside the tab name.
Here's the code:
MainView.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.cell.*?>
<?import javafx.collections.*?>
<TabPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" tabClosingPolicy="ALL_TABS" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainViewController">
<tabs>
<Tab text="Mailbox" closable="false">
<content>
<BorderPane prefHeight="200.0" prefWidth="200.0">
<top>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<Button fx:id="update" mnemonicParsing="false" onAction="#updateButton" text="Update" />
<Button fx:id="write" mnemonicParsing="false" onAction="#writeButton" text="Write" />
<Button fx:id="reply" mnemonicParsing="false" onAction="#replyButton" text="Reply" />
<Button fx:id="replyToAll" mnemonicParsing="false" onAction="#replyToAllButton" text="Reply to All" />
<Button fx:id="forward" mnemonicParsing="false" onAction="#forwardButton" text="Forward" />
<Button fx:id="delete" mnemonicParsing="false" onAction="#deleteButton" text="Delete" />
</items>
</ToolBar>
</top>
<bottom>
<HBox prefHeight="19.0" prefWidth="600.0" BorderPane.alignment="CENTER">
<children>
<Label text="Client Status:" />
<Label fx:id="status" text="" />
</children>
</HBox>
</bottom>
<left>
<Accordion BorderPane.alignment="CENTER">
<panes>
<TitledPane fx:id="mailboxName" animated="false" text="Inbox: ">
<content>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Label fx:id="inbox" text="Inbox" />
<Label fx:id="drafts" text="Drafts" />
<Label fx:id="bin" text="Bin" />
</children>
</VBox>
</content>
</TitledPane>
</panes>
</Accordion>
</left>
<center>
<TableView fx:id="table" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn prefWidth="247.0" text="Subject">
<cellValueFactory><PropertyValueFactory property="subject" /></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="131.0" text="From">
<cellValueFactory><PropertyValueFactory property="receiver" /></cellValueFactory>
</TableColumn>
<TableColumn minWidth="3.0" prefWidth="118.0" text="Date">
<cellValueFactory><PropertyValueFactory property="date" /></cellValueFactory>
</TableColumn>
</columns>
<items>
<FXCollections fx:factory="observableArrayList">
</FXCollections>
</items>
</TableView>
</center>
</BorderPane>
</content>
</Tab>
</tabs>
</TabPane>
MainViewController.java:
package controller;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import model.Account;
import model.Email;
import java.io.IOException;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;
public class MainViewController implements Initializable, Observer {
// utility https://stackoverflow.com/questions/40557492/mvc-with-javafx-and-fxml
// we will follow method 1
public MainViewController() {}
#FXML // MainView components
public TabPane root;
public Button update;
public Button write;
public Button reply;
public Button replyToAll;
public Button forward;
public Button delete;
public Label status;
public TitledPane mailboxName;
public Label inbox;
public Label drafts;
public Label bin;
public TableView<Email> table;
// BUTTONS ---------------------------------------------------------------------------------------------------------
/**
* On click on Update button do something
*/
#FXML
public void updateButton() {
update.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
status.setText(mess);
}
});
}
/**
* On click on Write button do something
*/
#FXML
public void writeButton() {
write.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
onWriteClick();
}
});
}
/**
* On click on Reply button do something
*/
#FXML
public void replyButton() {
reply.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
}
});
}
/**
* On click on Reply To All button do something
*/
#FXML
public void replyToAllButton() {
replyToAll.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
}
});
}
/**
* On click on Forward button do something
*/
#FXML
public void forwardButton() {
forward.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
}
});
}
/**
* On click on Delete label do something
*/
#FXML
public void deleteButton() {
delete.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
}
});
}
/**
* Sets the name of the account in the MainView
*/
#FXML
public void setMailboxName() {
}
// TODO: implementare lo stato nelle email, perche così possiamo discriminare se sono inbox, bin, drafts
/**
* On click on Inbox label do something
*/
#FXML
public void inboxLabel() {
}
/**
* On click on Drafts label do something
*/
#FXML
public void draftsLabel() {
}
/**
* On click on Bin label do something
*/
#FXML
public void binLabel() {
}
// INITIALIZING ----------------------------------------------------------------------------------------------------
/**
* It initialize all the event of the buttons
*/
private void initializeButtons(){
updateButton();
writeButton();
replyButton();
replyToAllButton();
forwardButton();
deleteButton();
}
/**
* It calls all the methods that initialize a category of components
*/
private void initializeAll() {
initializeButtons();
loadEmails();
}
// IMPLEMENTATIONS -------------------------------------------------------------------------------------------------
/**
* It initialize all the necessary for the GUI
* #param location: The location used to resolve relative paths for the root object, or null if the location is not known.
* #param resources: The resources used to localize the root object, or null if the root object was not localized.
*/
#Override
public void initialize(URL location, ResourceBundle resources) {
initializeAll();
System.out.println("GUI Loaded"); // DEBUG
}
/**
* Implementation of update method in Observer interface
* #param o: the observable object.
* #param arg: (optional) an argument passed to the notifyObservers method.
*/
#Override
public void update(Observable o, Object arg) {
}
// ON CLICK --------------------------------------------------------------------------------------------------------
/**
* It opens a new Tab with WriteView loaded. It is used to write a new email.
*/
private void onWriteClick(){
try{
Tab tab = new Tab("Write");
tab.setContent(FXMLLoader.load(getClass().getResource("/view/WriteView.fxml"))); // load the GUI for the Write tab
tab.setId("writeroot");
root.getTabs().add(tab); // Add the new tab beside the "Inbox" tab
root.getSelectionModel().select(tab); // Switch to Write tab
} catch (IOException e) {
e.printStackTrace();
}
}
// POPULATING MAIN VIEW --------------------------------------------------------------------------------------------
/**
* It populates the main view with all the email with a specific status
*/
private void loadEmails() {
}
} // end class
WriteView.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" 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="controller.WriteViewController">
<children>
<GridPane fx:id="table">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="296.0" minWidth="10.0" prefWidth="57.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="543.0" minWidth="10.0" prefWidth="543.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>
<Label text="To:" />
<Label text="From:" GridPane.rowIndex="1" />
<Label text="Subject:" GridPane.rowIndex="2" />
<TextField fx:id="to" GridPane.columnIndex="1" />
<TextField fx:id="from" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextField fx:id="subject" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>
<Pane prefHeight="272.0" prefWidth="600.0">
<children>
<TextArea fx:id="text" prefHeight="277.0" prefWidth="600.0" />
</children>
</Pane>
<ToolBar fx:id="toolbar" nodeOrientation="RIGHT_TO_LEFT" prefHeight="40.0" prefWidth="200.0">
<items>
<Button fx:id="send" mnemonicParsing="false" onAction="#sendButton" text="Send" />
<Button fx:id="saveAsDraft" mnemonicParsing="false" onAction="#saveButton" text="Save as draft" />
<Button fx:id="delete" mnemonicParsing="false" onAction="#deleteButton" text="Delete" />
</items>
</ToolBar>
</children>
</VBox>
WriteViewController.java:
package controller;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import model.Account;
import model.Email;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;
public class WriteViewController implements Initializable, Observer{
public WriteViewController() {
}
#FXML
public VBox root;
public GridPane table;
public TextField to;
public TextField from;
public TextField subject;
public TextArea text;
public ToolBar toolbar;
public Button send;
public Button saveAsDraft;
public Button delete;
// BUTTONS ---------------------------------------------------------------------------------------------------------
/**
* On click on Update button do something
*/
#FXML
public void sendButton() {
send.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Account reciver = new Account(to.getText());
Account sender = new Account(from.getText());
Email toSend = new Email(sender, reciver, subject.getText(), text.getText());
toSend.writeEmail(reciver, subject.getText(), text.getText());
// Is not necessary to set the state, because, when a new Email is created, it has already the
// state of new (2). For further information, see Email constructor.
}
});
}
#FXML
public void saveButton() {
saveAsDraft.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Account reciver = new Account(to.getText());
Account sender = new Account(from.getText());
Email toSend = new Email(sender, reciver, subject.getText(), text.getText());
toSend.setState(0); // the email is a draft
}
});
}
#FXML
public void deleteButton() {
delete.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Account reciver = new Account(to.getText());
Account sender = new Account(from.getText());
Email toSend = new Email(sender, reciver, subject.getText(), text.getText());
// here is the problem, Goal: close this tab!
System.out.println(root.getParent()); // DEBUG
}
});
}
// INITIALIZING ----------------------------------------------------------------------------------------------------
/**
* It initialize all the event of the buttons
*/
private void initializeButtons(){
sendButton();
saveButton();
deleteButton();
}
/**
* It call all the methods that initialize a category of components
*/
private void initializeAll() {
initializeButtons();
}
// IMPLEMENTATIONS -------------------------------------------------------------------------------------------------
/**
* It initialize all the necessary for the GUI
* #param location: The location used to resolve relative paths for the root object, or null if the location is not known.
* #param resources: The resources used to localize the root object, or null if the root object was not localized.
*/
#Override
public void initialize(URL location, ResourceBundle resources) {
initializeAll();
}
/**
* Implementation of update method in Observer interface
* #param o: the observable object.
* #param arg: (optional) an argument passed to the notifyObservers method.
*/
#Override
public void update(Observable o, Object arg) {
}
}
Client.java:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
/**
*
* #author lorenzotabasso
*/
public class Client extends Application{
#Override
public void start(Stage primaryStage) throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("view/MainView.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
You could pass a event handler to the controller that is triggered when you click the delete button:
private Runnable tabCloseHandler;
public void setTabCloseHandler(Runnable handler) {
tabCloseHandler = handler;
}
#FXML
public void deleteButton(ActionEvent e) {
// you don't set the handler here; this method is invoked as handler
String mess = "You clicked: " + e.getSource() + "!";
System.out.println(mess);
if (handler != null) {
handler.run();
}
}
final Tab tab = new Tab("Write");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/WriteView.fxml"));
tab.setContent(loader.load());
loader.<WriteViewController>getController().setTabCloseHandler(() -> root.getTabs().remove(tab));
Alternatively you could iterate up towards the root of the scene and close the shown tab as soon as you find a TabPane
public static TabPane findEnclosingTabPane(Node n) {
while (n != null && !(n instanceof TabPane)) {
n = n.getParent();
}
return (TabPane) n;
}
TabPane tabPane = findEnclosingTabPane(root);
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());

tableview not showing data from database

I am unable to populate my tableview. I believe the problem is in the controller, in the way my data is being sent to the FXML file, because a system out print (see below) shows exactly what I have in my database.
Please let me know where I did a mistake. I went over all tutorials that exist on that, but nothing fits my problem.
Thanks
Main App:
package tableview;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
public static void main(String[] args) {
// TODO Auto-generated method stub
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("view/FXMLTable.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Controller:
import tableview.model.Person;
public class FXMLTableController{
#FXML
public TableView<Person> tableview ;
#FXML
private TableColumn<Person, Number> clientIdColumn;
#FXML
private TableColumn<Person, String> firstNameColumn;
#FXML
private TableColumn<Person, String> lastNameColumn;
#FXML
private void initialize() {
assert tableview != null : "fx:id=\"tableview\" was not injected: check your FXML file 'UserMaster.fxml'.";
clientIdColumn.setCellValueFactory(cellData -> cellData.getValue().
clientIDProperty());
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue()
.firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue()
.lastNameProperty());
buildData();
}
private ObservableList<Person> data;
public void buildData(){
data = FXCollections.observableArrayList();
Connection con = null;
try {
Class.forName("org.sqlite.JDBC");
con = DriverManager.getConnection("jdbc:sqlite:tableviewdb.db");
String SQL = "Select * from INFO";
ResultSet rs = con.createStatement().executeQuery(SQL);
while(rs.next()){
Person per = new Person();
per.ClientID.set(rs.getInt("CLIENTID"));
per.FirstName.set(rs.getString("FIRSTNAME"));
per.LastName.set(rs.getString("LASTNAME"));
data.add(per);
}
tableview = new TableView<Person>();
tableview.setItems(data);
System.out.println(tableview.getItems().get(1).ClientID);
}
catch(Exception e){
e.printStackTrace();
System.out.println("Error on Building Data");
}
}
}
Model Class:
package tableview.model;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
public SimpleIntegerProperty ClientID = new SimpleIntegerProperty();
public SimpleStringProperty FirstName = new SimpleStringProperty();
public SimpleStringProperty LastName = new SimpleStringProperty();
public SimpleIntegerProperty getClientID() {
return ClientID;
}
public SimpleStringProperty getFirstname() {
return FirstName;
}
public SimpleStringProperty getLastName() {
return LastName;
}
public IntegerProperty clientIDProperty(){
return ClientID;
}
public StringProperty firstNameProperty(){
return FirstName;
}
public StringProperty lastNameProperty(){
return LastName;
}
}
FXML file:
(disregard the save button for now...)
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
<children>
<SplitPane prefHeight="400.0" prefWidth="600.0">
<items>
<SplitPane dividerPositions="0.5" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TextField layoutX="93.0" layoutY="34.0" />
<TextField layoutX="93.0" layoutY="85.0" />
<Label layoutX="35.0" layoutY="39.0" text="name" />
<Label layoutX="35.0" layoutY="90.0" text="email" />
<Button layoutX="204.0" layoutY="140.0" mnemonicParsing="false" text="save" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TableView layoutY="-2.0" prefHeight="200.0" prefWidth="598.0">
<columns>
<TableColumn prefWidth="302.0" text="name" />
<TableColumn prefWidth="295.0" text="email" />
</columns>
</TableView>
</children>
</AnchorPane>
</items>
</SplitPane>
</items>
</SplitPane>
</children>
</AnchorPane>
You're creating a new TableView and setting its items, instead of setting the items on the table that the FXML file defined. Remove the
tableView = new TableView<Person>();
from the controller.
To get the #FXML-annotated fields in the controller to be populated with the appropriate elements from the FXML file, you need to add fx:id attributes to those elements:
<TableView fx:id="tableview" ... >
<columns>
<TableColumn fx:id="firstNameColumn" ... />
...

Categories