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>
Related
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);
}
}
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).
Goal:
Display data, below, inside of the table
"Jacob", "Smith", "jacob.smith#example.com"
"Isabella", "Johnson", "isabella.johnson#example.com"
"Ethan", "Williams", "ethan.williams#example.com"
"Emma", "Jones", "emma.jones#example.com"
"Michael", "Brown", "michael.brown#example.com"
Problem:
What code am I missing in order to display the data inside of the table?
Info:
I'm new in JavaFX.
I'm using netbeans
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="787.0" prefWidth="1086.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<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>
<center>
<TableView id="ttt" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn id="firstNameCol" prefWidth="124.0" text="First name" />
<TableColumn id="lastNameCol" prefWidth="139.0" text="Last name" />
<TableColumn id="emailCol" minWidth="7.0" prefWidth="197.0" text="Email" />
<TableColumn id="addressCol" prefWidth="105.0" text="Address" />
<TableColumn id="zipcodeCol" prefWidth="100.0" text="Zipcode" />
<TableColumn id="cityCol" prefWidth="204.0" text="City" />
</columns>
</TableView>
</center>
</BorderPane>
package dreamcrm;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DreamCRM extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
package dreamcrm;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
package dreamcrm;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
public Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
You will want to use fx:id instead of id for this. fx:id will still work with CSS selectors, so no worries there.
As #fabian pointed out, and that I missed, you also need to add the fx:controller in your fxml file so that it knows what is going to control it.
In your document controller, you will need to let it know it is getting some info from your fxml document using #FXML
So like this,
#FXML
private TableView ttt;
#FXML
private TableColumn<Person, String> firstNameCol, lastNameCol, emailCol,addressCol, zipCol, cityCol;
Here, you use your fx:id as the variable name.
Then, in your initialize function, you will need to set the cellValueFactory, like so.
#FXML
public void initialize(){
firstNamecol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
//etc..
//Don't forget to add your list that you made to the tableview
ttt.setItems(list);
}
After you add the ObservableList you can now add and remove items and it should update accordingly.
On a related note, you need to have SimpleStringProperty as a returnable value, i.e.
public StringProperty firstNameProperty(){
return firstName;
}
This should get you started.
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).
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" ... />
...