I've been trying to load a TableView with data queried from a database, but can't seem to get it to work.
This is my first attempt at trying to populate a TableView with database query items – in case my code seems mungled and far from good.
The FXML was done via JavaFX SceneBuilder.
This is the database query class:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableView;
public class StudentInfo {
static String JDBC_DRIVER = "org.h2.Driver";
static String DB_URL = "jdbc:h2:file:C:/WAKILI/WAKILIdb";
// Database credentials
static final String USER = "sa";
static final String PASS = "";
public static Connection conn = null;
#FXML
private TableView<StudentInfo> lovelyStudents;
private ObservableList data;
// Public static ObservableList<COA> getAllCOA(){
public void getAllstudentInfo() {
Statement st = null;
ResultSet rs;
String driver = "org.h2.Driver";
try {
Class.forName(driver);
conn = DriverManager.getConnection(DB_URL, USER, PASS);
st = conn.createStatement();
String recordQuery = ("SELECT id, KIWI FROM KIWI");
rs = st.executeQuery(recordQuery);
while (rs.next()) {
ObservableList row = FXCollections.observableArrayList();
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
row.add(rs.getString(i));
System.out.println(row);
}
data.add(row);
}
lovelyStudents.setItems(data);
} catch (ClassNotFoundException | SQLException ex) {
// CATCH SOMETHING
}
}
}
This is the FXML script generated via JavaFX scene builder:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="700.0" xmlns:fx="http://javafx.com/fxml" fx:controller="wakiliproject.SampleController">
<children>
<TableView prefHeight="400.0" prefWidth="700.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
</children>
</AnchorPane>
Here is the best solution for the filling data to the tableView From the database.
import java.sql.Connection;
import java.sql.ResultSet;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author Narayan
*/
public class DynamicTable extends Application{
//TABLE VIEW AND DATA
private ObservableList<ObservableList> data;
private TableView tableview;
//MAIN EXECUTOR
public static void main(String[] args) {
launch(args);
}
//CONNECTION DATABASE
public void buildData(){
Connection c ;
data = FXCollections.observableArrayList();
try{
c = DBConnect.connect();
//SQL FOR SELECTING ALL OF CUSTOMER
String SQL = "SELECT * from CUSTOMer";
//ResultSet
ResultSet rs = c.createStatement().executeQuery(SQL);
/**********************************
* TABLE COLUMN ADDED DYNAMICALLY *
**********************************/
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++){
//We are using non property style for making dynamic table
final int j = i;
TableColumn col = new TableColumn(rs.getMetaData().getColumnName(i+1));
col.setCellValueFactory(new Callback<CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
tableview.getColumns().addAll(col);
System.out.println("Column ["+i+"] ");
}
/********************************
* Data added to ObservableList *
********************************/
while(rs.next()){
//Iterate Row
ObservableList<String> row = FXCollections.observableArrayList();
for(int i=1 ; i<=rs.getMetaData().getColumnCount(); i++){
//Iterate Column
row.add(rs.getString(i));
}
System.out.println("Row [1] added "+row );
data.add(row);
}
//FINALLY ADDED TO TableView
tableview.setItems(data);
}catch(Exception e){
e.printStackTrace();
System.out.println("Error on Building Data");
}
}
#Override
public void start(Stage stage) throws Exception {
//TableView
tableview = new TableView();
buildData();
//Main Scene
Scene scene = new Scene(tableview);
stage.setScene(scene);
stage.show();
}
}
Here is the Reference
Thanks..
If Database contains different types of data, not only String, then column type assigning is better to make dynamic:
package sample;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import java.sql.*;
import java.util.ArrayList;
import java.util.TimeZone;
//Author: Yerbol
//SQL database "sqlbase_schema" contains a Table "sqlbase_table" with 3 columns: "id" (Integer(INT(11))), "name" (String(VARCHAR(45))), "married" (Boolean(TINYINT(1)));
public class Main extends Application {
private TableView<Person> tableView = new TableView<>();
#Override
public void start(Stage primaryStage) throws SQLException, ClassNotFoundException {
//Show window
buildData();
Parent root = tableView;
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public void buildData() throws ClassNotFoundException, SQLException {
Connection dbConnection;
//SQL Database connection params
String dbHost = "localhost";
String dbPort = "3306";
String dbUser = "root";
String dbPassword = "12345";
String dbName = "sqlbase_schema";
String dbTableName = "sqlbase_table";
String select = "SELECT * FROM " + dbTableName;
String connectionString = "jdbc:mysql://" + dbHost + ":" + dbPort +"/" + dbName+"?useLegacyDatetimeCode=false&&serverTimezone=" + TimeZone.getDefault().getID();
Class.forName("com.mysql.cj.jdbc.Driver");
//Connecting to Database
dbConnection = DriverManager.getConnection(connectionString, dbUser, dbPassword);
//Extracting data from Databasee
ResultSet resultSet = null;
try {
PreparedStatement preparedStatement = dbConnection.prepareStatement(select);
resultSet = preparedStatement.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
ObservableList dbData = FXCollections.observableArrayList(dataBaseArrayList(resultSet));
//Giving readable names to columns
for(int i=0 ; i<resultSet.getMetaData().getColumnCount(); i++) {
TableColumn column = new TableColumn<>();
switch (resultSet.getMetaData().getColumnName(i+1)) {
case "id":
column.setText("ID #");
break;
case "name":
column.setText("Person Name");
break;
case "married":
column.setText("Marital Status");
break;
default: column.setText(resultSet.getMetaData().getColumnName(i+1)); //if column name in SQL Database is not found, then TableView column receive SQL Database current column name (not readable)
break;
}
column.setCellValueFactory(new PropertyValueFactory<>(resultSet.getMetaData().getColumnName(i+1))); //Setting cell property value to correct variable from Person class.
tableView.getColumns().add(column);
}
//Filling up tableView with data
tableView.setItems(dbData);
}
public class Person {
IntegerProperty id = new SimpleIntegerProperty(); //variable names should be exactly as column names in SQL Database Table. In case if you want to use <int> type instead of <IntegerProperty>, then you need to use getter/setter procedures instead of xxxProperty() below
StringProperty name = new SimpleStringProperty();
BooleanProperty married = new SimpleBooleanProperty();
public IntegerProperty idProperty() { //name should be exactly like this [IntegerProperty variable name (id) + (Property) = idProperty] (case sensitive)
return id;
}
public StringProperty nameProperty() {
return name;
}
public BooleanProperty marriedProperty() {
return married;
}
public Person(int idValue, String nameValue, boolean marriedValue) {
id.set(idValue);
name.set(nameValue);
married.set(marriedValue);
}
Person(){}
}
//extracting data from ResulSet to ArrayList
private ArrayList dataBaseArrayList(ResultSet resultSet) throws SQLException {
ArrayList<Person> data = new ArrayList<>();
while (resultSet.next()) {
Person person = new Person();
person.id.set(resultSet.getInt("id"));
person.name.set(resultSet.getString("name"));
person.married.set(resultSet.getBoolean("married"));
data.add(person);
}
return data;
}
public static void main(String[] args) {
launch(args);
}
}
In this example SQL database "sqlbase_schema" contains a Table "sqlbase_table" with 3 columns: "id" (Integer(INT(11))), "name" (String(VARCHAR(45))), "married (Boolean(TINYINT(1)));
public TableView queryToTable(String sql) {
TableView result = new TableView();
ObservableList data = FXCollections.observableArrayList();
jdbcTemplate.query(sql, (rs)->{
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++){
final int j = i;
TableColumn col = new TableColumn(rs.getMetaData().getColumnName(i+1));
col.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(TableColumn.CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
result.getColumns().addAll(col);
}
while(rs.next()){
ObservableList<String> row = FXCollections.observableArrayList();
for(int i=1 ; i<=rs.getMetaData().getColumnCount(); i++)
row.add(rs.getString(i));
data.add(row);
}
return null;
});
return result;
}
I have a TableView which contains textual and graphical content (combo boxes, check boxes etc).
When I traverse the cells using the keyboard and arrive at a cell that contains a graphic element, I would like the graphic to be selected so that I can, for example, hit F4 and have a combo list drop down, or hit the space bar and have a toggle button change state.
However, at the moment, when I TAB (or other key) to a cell, the cell containing the graphic is selected and I'm forced to use the mouse to manipulate the graphic.
How would I go about selecting the graphic element itself, rather than the cell that contains it?
IE. This is what it's doing now when I TAB into a non-textual cell:
How can I get it to do this?
I've tried several ways of getting the cell graphic but it's always null.
UPDATE:
I've done more work and can now get to the cell graphic. It was a Java newbie error. Apologies!
However, while I can now get the graphic, I still haven't been able to select or focus on it. Could anyone tell me how to do that please? Many thanks!
Here are excerpts from my updated code using combo boxes and TABbing as an example.
Key events are trapped in a generic setOnKeyPressed handler at the TableView level. Here is the code for TAB. I've indicated the places where I'm stuck.
} else if ( event.getCode() == KeyCode.TAB ) {
tv.getSelectionModel().selectRightCell();
endOfRowCheck(tv, event, pos, firstCol, maxCols);
event.consume();
//==> IS IT BETTER TO USE THE FOCUS MODEL OR THE SELECTION MODEL? BOTH GIVE THE CELL GRAPHIC.
//==> IS THERE A BETTER WAY OF GETTING THE CELL GRAPHIC?
TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
Node cellGraphic = cell.getGraphic();
System.out.println(cellGraphic);
//Output: ComboBox#44cf20e7[styleClass=combo-box-base combo-box]
//==> HOW DO I NOW FOCUS ON (OR SELECT?) THE GRAPHIC?
//I tried Platform.runLater() on the requestFocus but that didn't work either.
cellGraphic.requestFocus();
} else if ...
For completeness, here's the called endOfRowCheck method:
private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {
if ( pos.getColumn() == maxCols ) {
//We're at the end of a row so position to the start of the next row
tv.getSelectionModel().select(pos.getRow()+1, col);
event.consume();
}
}
I create combo box columns as follows.
In the FXML controller:
TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumnTEST(colComboBoxField_HEADING, TestModel::comboBoxFieldProperty, arlMasterAssetClasses);
In the DAOGenUtil class:
public <S> TableColumn<S, DBComboChoice> createComboBoxColumnTEST(String title,
Function<S, StringProperty> methodGetComboFieldProperty,
ObservableList<DBComboChoice> comboData) {
TableColumn<S, DBComboChoice> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> {
String masterCode = methodGetComboFieldProperty.apply(cellData.getValue()).get();
DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
return new SimpleObjectProperty<>(choice);
});
col.setCellFactory(column -> ComboBoxCell.createComboBoxCell(comboData));
return col;
}
The ComboBoxCell class, which I use to render non-editable combos as combos and not as labels.
public class ComboBoxCell<S, T> extends TableCell<S, T> {
private final ComboBox<DBComboChoice> combo = new ComboBox<>();
public ComboBoxCell(ObservableList<DBComboChoice> comboData) {
combo.getItems().addAll(comboData);
combo.setEditable(false);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
combo.setOnAction((ActionEvent event) -> {
try {
String masterCode = combo.getSelectionModel().getSelectedItem().getMasterCode();
S datamodel = getTableView().getItems().get(getIndex());
try {
Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
mSetComboBoxField.invoke(datamodel, masterCode);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex) {
System.err.println(ex);
DAOGenUtil.logError(ex.getClass().toString(), ex.getMessage(), "Call to 'setComboBoxField' failed in ComboBoxCell.setOnAction for master code '" + masterCode + "'");
}
} catch (NullPointerException ex) {
//temporary workaround for bad test data
System.out.println("caught NPE in combo.setOnAction");
}
});
}
public static <S> ComboBoxCell<S, DBComboChoice> createComboBoxCell(ObservableList<DBComboChoice> comboData) {
return new ComboBoxCell<S, DBComboChoice>(comboData);
}
#Override
protected void updateItem(T comboChoice, boolean empty) {
super.updateItem(comboChoice, empty);
if (empty) {
setGraphic(null);
} else {
combo.setValue((DBComboChoice) comboChoice);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
I'm using JavaFX8, NetBeans 8.2 and Scene Builder 8.3.
UPDATED AGAIN:
Here is a full test case as requested, reproducible in NetBeans. My apologies if it's not in an expected format ... I'm still relatively new to Java and don't know how to turn it into something you can run standalone.
If you click in the text field column and then TAB to the combo box column, the cell that contains the combo box gets the focus, not the combo box itself.
For my app, the combo boxes need to be non-editable and always rendered as combos. When the user reaches the end of a table row and hits TAB (or RIGHT ARROW), the focus needs to move to the start of the next row.
Here is the test case code.
The app:
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Test extends Application {
#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();
}
public static void main(String[] args) {
launch(args);
}
}
The FXML controller:
package test;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.Observable;
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;
public class FXMLDocumentController implements Initializable {
private DAOGenUtil DAOGenUtil = new DAOGenUtil();
public ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel ->
new Observable[] {
testmodel.textFieldProperty(),
testmodel.comboBoxFieldProperty()
});
ObservableList<DBComboChoice> comboChoices = FXCollections.observableArrayList();
TableColumn<TestModel, String> colTextField = new TableColumn("text col");
TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumn("combo col", TestModel::comboBoxFieldProperty, comboChoices);
#FXML
private TableView<TestModel> tv;
#Override
public void initialize(URL url, ResourceBundle rb) {
comboChoices.add(new DBComboChoice("F", "Female"));
comboChoices.add(new DBComboChoice("M", "Male"));
olTestModel.add(new TestModel("test row 1", "M"));
olTestModel.add(new TestModel("test row 2", "F"));
olTestModel.add(new TestModel("test row 3", "F"));
olTestModel.add(new TestModel("test row 4", "M"));
olTestModel.add(new TestModel("test row 5", "F"));
colTextField.setCellValueFactory(new PropertyValueFactory<>("textField"));
tv.getSelectionModel().setCellSelectionEnabled(true);
tv.setEditable(true);
tv.getColumns().addAll(colTextField, colComboBoxField);
tv.setItems(olTestModel);
tv.setOnKeyPressed(event -> {
TableColumn firstCol = colTextField;
TableColumn lastCol = colComboBoxField;
int firstRow = 0;
int lastRow = tv.getItems().size()-1;
int maxCols = 1;
DAOGenUtil.handleTableViewSpecialKeys(tv, event, firstCol, lastCol, firstRow, lastRow, maxCols);
});
}
}
The ComboBoxCell class:
package test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
public class ComboBoxCell<S, T> extends TableCell<S, T> {
private final ComboBox<DBComboChoice> combo = new ComboBox<>();
private final DAOGenUtil DAOGenUtil;
public ComboBoxCell(ObservableList<DBComboChoice> comboData) {
this.DAOGenUtil = new DAOGenUtil();
combo.getItems().addAll(comboData);
combo.setEditable(false);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
combo.setOnAction((ActionEvent event) -> {
String masterCode = combo.getSelectionModel().getSelectedItem().getMasterCode();
S datamodel = getTableView().getItems().get(getIndex());
try {
Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
mSetComboBoxField.invoke(datamodel, masterCode);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex) {
System.err.println(ex);
}
});
}
#Override
protected void updateItem(T comboChoice, boolean empty) {
super.updateItem(comboChoice, empty);
if (empty) {
setGraphic(null);
} else {
combo.setValue((DBComboChoice) comboChoice);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
The TableView data model:
package test;
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
public class TestModel {
private StringProperty textField;
private StringProperty comboBoxField;
public TestModel() {
this(null, null);
}
public TestModel(
String textField,
String comboBoxField
) {
this.textField = new SimpleStringProperty(textField);
this.comboBoxField = new SimpleStringProperty(comboBoxField);
}
public String getTextField() {
return textField.get().trim();
}
public void setTextField(String textField) {
this.textField.set(textField);
}
public StringProperty textFieldProperty() {
return textField;
}
public String getComboBoxField() {
return comboBoxField.get().trim();
}
public void setComboBoxField(String comboBoxField) {
this.comboBoxField.set(comboBoxField);
}
public StringProperty comboBoxFieldProperty() {
return comboBoxField;
}
}
The DBComboChoice data model:
package test;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
public class DBComboChoice {
private StringProperty masterCode;
private StringProperty masterDescription;
public DBComboChoice(
String masterCode,
String masterDescription
) {
this.masterCode = new SimpleStringProperty(masterCode);
this.masterDescription = new SimpleStringProperty(masterDescription);
}
public String getMasterCode() {
return masterCode.get();
}
public StringProperty masterCodeProperty() {
return masterCode;
}
public String getMasterDescription() {
return masterDescription.get();
}
public StringProperty masterDescriptionProperty() {
return masterDescription;
}
public static DBComboChoice getDescriptionByMasterCode(String inMasterCode, ObservableList<DBComboChoice> comboData) {
for ( int i=0; i<comboData.size(); i++ ) {
if ( comboData.get(i).getMasterCode().equals(inMasterCode) ) {
return comboData.get(i);
}
}
return null;
}
#Override
public String toString() {
return this.masterDescription.get();
}
}
The DAOGenUtil class:
package test;
import java.util.function.Function;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class DAOGenUtil {
public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title,
Function<S, StringProperty> methodGetComboFieldProperty,
ObservableList<DBComboChoice> comboData) {
TableColumn<S, DBComboChoice> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> {
String masterCode = methodGetComboFieldProperty.apply(cellData.getValue()).get();
DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
return new SimpleObjectProperty<>(choice);
});
col.setCellFactory((TableColumn<S, DBComboChoice> param) -> new ComboBoxCell<>(comboData));
return col;
}
public <S> void handleTableViewSpecialKeys(TableView tv, KeyEvent event,
TableColumn firstCol, TableColumn lastCol,
int firstRow, int lastRow,
int maxCols) {
//NB: pos, at this point, is the cell position that the cursor is about to leave
TablePosition<S, ?> pos = tv.getFocusModel().getFocusedCell();
if (pos != null ) {
if ( event.getCode() == KeyCode.TAB ) {
tv.getSelectionModel().selectRightCell();
endOfRowCheck(tv, event, pos, firstCol, maxCols);
event.consume();
TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
Node cellGraphic = cell.getGraphic();
System.out.println("node cellGraphic is " + cellGraphic);
if ( cellGraphic instanceof ComboBox<?> ) {
System.out.println("got a combo");
//nbg cellGraphic.requestFocus();
Platform.runLater(() -> {
((ComboBox<?>) cellGraphic).requestFocus();
});
}
} else if ( ! event.isShiftDown() && ! event.isControlDown() ){
//edit the cell
tv.edit(pos.getRow(), pos.getTableColumn());
}
}
}
private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {
if ( pos.getColumn() == maxCols ) {
//We're at the end of a row so position to the start of the next row
tv.getSelectionModel().select(pos.getRow()+1, col);
event.consume();
}
}
}
The FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.FXMLDocumentController">
<center>
<TableView fx:id="tv" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Your question is too broad, you are trying to tackle too many problems at once, f.i.:
change navigation sequence, that is endOfRow handling
use tab for navigation
implement a custom cell
map visual appearence of a value to something with meaning for the user
start editing (aka: "focus") on reaching a cell
The most important (IMO) misconception in your code is the last bullet: you must not by-pass the editing mechanism when changing the underlying data. So change thinking "focus the graphic" to "start edit".
Below is a stand-alone example that demonstrates how to start with core support and modify it to get nearer to what's really needed. It
configures a core ComboBoxTableCell with a (crude ;) StringConverter to do the mapping of masterCode -> masterDescription
extends that core cell to request focus in startEdit
registers a listener on the table's (actually its focusModel's) focusedCell property that starts editing the new cell
Options to proceed from here:
to show the editable control always, look at the code of ComboBoxTableCell and modify to show the combo always
re-apply the tab handling (looks fine to me)
change navigation sequence as needed
The code:
public class TableCellFocusApp extends Application {
private Parent createContent() {
ObservableList<TestModel> olTestModel = FXCollections
.observableArrayList(testmodel -> new Observable[] {
testmodel.textFieldProperty(),
testmodel.comboBoxFieldProperty() });
TableView<TestModel> table = new TableView<>();
olTestModel.add(new TestModel("test row 1", "M"));
olTestModel.add(new TestModel("test row 2", "F"));
olTestModel.add(new TestModel("test row 3", "F"));
olTestModel.add(new TestModel("test row 4", "M"));
olTestModel.add(new TestModel("test row 5", "F"));
TableColumn<TestModel, String> colTextField = new TableColumn<>("text col");
colTextField
.setCellValueFactory(cb -> cb.getValue().textFieldProperty());
TableColumn<TestModel, String> gender= new TableColumn<>("Gender");
gender.setMinWidth(100);
gender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
StringConverter<String> converter = new StringConverter<>() {
#Override
public String toString(String object) {
return "F".equals(object) ? "Female" : "Male";
}
#Override
public String fromString(String string) {
return "Female".equals(string) ? "F" : "M";
}
};
gender.setCellFactory(cb -> new ComboBoxTableCell<>(converter, "F", "M") {
#Override
public void startEdit() {
super.startEdit();
if (getGraphic() != null) {
getGraphic().requestFocus();
}
}
});
// just to see that the data is updated correctly - add a readonly column
TableColumn<TestModel, String> plainGender = new TableColumn<>("readonly");
plainGender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
plainGender.setEditable(false);
table.getFocusModel().focusedCellProperty().addListener((src, ov, nv) -> {
if (nv != null && nv.getTableColumn() == gender) {
table.edit(nv.getRow(), gender);
}
});
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);
table.getColumns().addAll(colTextField,gender, plainGender ); //, colComboBoxField );
table.setItems(olTestModel);
BorderPane content = new BorderPane(table);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCellFocusApp.class.getName());
}
This question already has answers here:
JavaFX Error: Controller value already specified
(1 answer)
javafx exception : Controller value already specified
(1 answer)
Closed 4 years ago.
I have looked at the posts for this and Im still stuck - any help please?
I am trying to display a line chart in scenebuilder (IntelliJ) - but keep getting different errors about constructors.The last error was
'JavaFX Error : Controller already specified'but have removed it previously and still get an error.
and have tried different arguments in Main for FXMLLoader with the loader.setController(...) line, and no luck. I keep reading about sometimes factory controllers are needed if arguments are being passed to the constructor - so think it might be this, but dont know how to write one.Any suggestions please? or good links for me to read up?
My last attempt for FXMLLoaderCode from Main below...
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample/view/graph.fxml"));
loader.setController(new Controller(sortList));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
If anybody has any useful links so I could read up about factory controllers that would be really good. My full code is below.
My programs are
Main(Main.java)
entry point for program and uses FXML loader
Model(SortList.java)
places numbers in x and y(abscissa ordinate) groups
Controller(Controller.java)
add x,y to graph
View(graph.fxml)
graph xml displays x-y line chart
ReadInData(ReadInData.java)
Reads in and parses numbers
Errors
javafx.fxml.LoadException: Controller value already specified.
/C:/Users/user/IdeaProjects/myjavafx/out/production/myjavafx/sample/view/graph.fxml:18
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2597)
at javafx.fxml.FXMLLoader.access$100(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:914)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
at sample.Main.start(Main.java:32)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
Model (SortList.java)
public class SortList {
private final double abscissa;
private final double ordinate;
public SortList(double abscissa, double ordinate) {
this.abscissa = abscissa;
this.ordinate = ordinate;
}
public double getabscissa() {
return abscissa;
}
public double getordinate() {
return ordinate;
}
}
View (graph.java)
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.chart.ScatterChart?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Label?>
<AnchorPane id="AnchorPane"
maxHeight="-Infinity"
maxWidth="-Infinity"
minHeight="-Infinity"
minWidth="-Infinity"
prefHeight="400.0"
prefWidth="600.0"
xmlns="http://javafx.com/javafx/8.0.121"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller = "sample.controller.Controller">
<children>
<ScatterChart layoutX="76.0" layoutY="33.0" prefHeight="335.0" prefWidth="449.0" title="Test Graph">
<xAxis>
<CategoryAxis label="X axis" pickOnBounds="false" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis label="Y axis" side="LEFT" upperBound="1000.0" />
</yAxis>
</ScatterChart>
</children>
</AnchorPane>
Controller (Controller.java)
package sample.controller;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import sample.model.SortList;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Controller implements Initializable {
public LineChart<Double, Double> lineChart;
private final ArrayList<SortList> sortList;
public LineChart label1;
public Controller(ArrayList<SortList> sortList)
{
this.sortList = sortList;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
XYChart.Series<Double, Double> graphLine = new XYChart.Series<Double, Double>();
for (SortList sortlist : sortList) {
graphLine.getData().add(new XYChart.Data(sortlist.getabscissa(), sortlist.getordinate()));
}
lineChart.getData().add(graphLine);
}
}
Main (Main.java)
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Callback;
import sample.IO.ReadInData;
import sample.controller.Controller;
import sample.model.SortList;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Main extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
final ArrayList<SortList> sortList = ReadInData.ReadData("C:\\Users\\user\\Desktop\\Java problem\\test.csv");
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample/view/graph.fxml"));
loader.setController(new Controller(sortList));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
loader.setControllerFactory(new Callback<Class<?>, Object>(){
#Override
public Object call(Class<?> param) {
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
public class SortList {
private final double abscissa;
private final double ordinate;
public SortList(double abscissa, double ordinate) {
this.abscissa = abscissa;
this.ordinate = ordinate;
}
public double getabscissa() {
return abscissa;
}
public double getordinate() {
return ordinate;
}
}
Input/Output Read in File (ReadInData.java)
package sample.IO;
import sample.model.SortList;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
public class ReadInData {
private static final String COMMA_DELIMITER = ",";
static public ArrayList<SortList> ReadData(String filename) {
BufferedReader br = null;
ArrayList<SortList> inputList = new ArrayList<SortList>();
try {
br = new BufferedReader(new FileReader(filename));
String line = "";
br.readLine();
while ((line = br.readLine()) != null) {
String[] inputData = line.split(COMMA_DELIMITER);
if (inputData.length > 0) {
try {
double abscissa = Double.parseDouble(inputData[0]);
double ordinate = Double.parseDouble(inputData[1]);
SortList row = new SortList(abscissa, ordinate);
inputList.add(row);
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return inputList;
}
}
Open your FXML file with Scene Builder and check this
I created a book management application via javafx. Through this app we can perform add, search and delete operations. For the search i am using Table view to get all the details of the book searched by the user. So in the end i am getting the result displayed in the console but not in the table view
package Managemnet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import connection.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
public class Searching {
Connection connect;
#FXML
TableView<Book> search_view ;
#FXML
ObservableList<Book> observeList;
#FXML
TableColumn<Book, Integer> Book_I;
#FXML
TableColumn<Book, String> Book_n;
#FXML
TableColumn<Book, String> publish;
#FXML
TableColumn<Book, Integer> ye;
#FXML
TextField sea_Book_name;
#FXML
TextField sea_pub;
#FXML
TextField sea_year;
#FXML
AnchorPane SearchPane;
#FXML
Button search;
public void search() throws ClassNotFoundException, SQLException{
Sql_query sql = new Sql_query();
sql.connect_sql();
connect = sql.getConnect();
observeList = FXCollections.<Book>observableArrayList();
search_view = new TableView<Book>();
Book_I=new TableColumn<Book ,Integer>();
Book_I.setCellValueFactory(new PropertyValueFactory<Book ,Integer>
("Book_id"));
Book_n=new TableColumn<Book ,String>();
Book_n.setCellValueFactory(new PropertyValueFactory<Book ,String>
("Book_name"));
publish=new TableColumn<Book ,String>();
publish.setCellValueFactory(new PropertyValueFactory<Book ,String>
("Publisher"));
ye=new TableColumn<Book ,Integer>();
ye.setCellValueFactory(new PropertyValueFactory<Book ,Integer>("year"));
String s1= sea_Book_name.getText();
String s2= sea_pub.getText();
String s= sea_year.getText();
int s3;
if(!s.isEmpty()){
s3=Integer.parseInt(s);
}
else s3=0;
ResultSet res;
Answer_from_dataset obj= new Answer_from_dataset();
res=obj.getAnswer(s1, s2, s3);
while(res.next()){
observeList.add(new Book(res.getInt(1), res.getString(2),
res.getString(3), res.getInt(4)));
//System.out.println(observeList);
}
search_view.setItems(observeList);
System.out.println(search_view);
}
package Managemnet;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class Book {
SimpleStringProperty Book_name;
SimpleStringProperty Publisher;
SimpleIntegerProperty Book_id;
SimpleIntegerProperty year;
public Book(int book_id, String book_name, String publisher, int year) {
this.Book_name =new SimpleStringProperty(book_name);
this.Publisher =new SimpleStringProperty(publisher);
this.Book_id =new SimpleIntegerProperty (book_id);
this.year = new SimpleIntegerProperty(year);
}
public String getBook_name() {
return Book_name.get();
}
public String getPublisher() {
return Publisher.get();
}
public Integer getBook_id() {
return Book_id.get();
}
public Integer getYear() {
return year.get();
}
public void setBook_name(String book_name) {
Book_name.set(book_name);
}
public void setPublisher(String publisher) {
Publisher.set(publisher);;
}
public void setBook_id(int book_id) {
Book_id.set(book_id);;
}
public void setYear(int year) {
this.year.set(year);;
}
#Override
public String toString() {
return getBook_id() + " " + getBook_name() + " " + getPublisher() + " "
+ getYear();
}
}
So I ran into this issue with FXML when I did my first project with JavaFX.
I tried a lot of solutions with FXML but it took time and nothing worked. So I shifted over to using POJO's. This may not be ideally what you're looking for but it would give you a pretty good amount of flexibility with creating your table.
Ideally for the table itself you want to create a TableView
TableView<Book> table = new TableView();
Then for each column you want to create a TableColumn.. Now here you need to be careful
TableColumn<Book, String> col1 = new TableColumn<>("Name");
col.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName()));
The values within the angle brackets is <Object Type, Data Type>
And then of course you can set your preferred width and what not. But the same process of creating TableColumns Repeats itself for the table you have. I get this might be a little more of lengthier process but its relatively easy to understand and most importantly it work.
Try this and let me know if it works! Cheers!!
Edited and removed all unused code
New to Javafx / scene builder using JDK 8 and eclipse.
Sql DB connection working fine and pulls to a recordset which populates a virtual Tableview, system.out prints db records etc. I am using scene builder and trying to populate a FXML defined Tableview in scenebuilder, which is fun to learn.
I just cannot get the data to the tableview.
I added static to private static ObservableList<ObservableList<String>> data; which has stopped my nullPointerException and added public void initialize(URL location, ResourceBundle resources) which tells me the ObservableList data has SOME DATA and watched way to many youtube videos.
I now have no errors but see no data in the defined tableview. When i add a column in to scenebulder without an id, i get different coloured rows, which makes me think it is doing sometihng, controller is attached in scenebuilder.
I just wanted to pull all the table columns for now just to test and then i can go on from there. Apologies for the messy code but may as well leave it in, first week.
I would be grateful for any assistance, really would.
Controller, left out imports
public class SoftwareController extends Application implements Initializable {
private static ObservableList<ObservableList<String>> data;
#FXML
public TableView<ObservableList<String>> tblSoftware;
public Statement st;
public Connection conn;
public static void main(String[] args) {
launch(args);
}
public void buildData() {
data = FXCollections.observableArrayList();
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
String url = "jdbc:sqlserver://IP;databaseName=CMDB";
conn = DriverManager.getConnection(url,"cmdbadmin","cmdbadmin!1");
System.out.print("connection successfulltttt");
String SQL = "SELECT * from Data_CMDB_Main";
ResultSet rs = conn.createStatement().executeQuery(SQL);
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn col = new TableColumn(rs.getMetaData().getColumnName(i+1));
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
System.out.println(col);
}
while (rs.next()){
ObservableList<String> row = FXCollections.observableArrayList();
for(int i=1 ; i<=rs.getMetaData().getColumnCount(); i++) {
row.add(rs.getString(i));
}
data.add(row);
System.out.println(row); //shows records from database
}
}catch (Exception e) {
e.printStackTrace();
System.out.println("Error building data");
}
}
#Override
public void start(Stage stage) throws Exception {
buildData();
}
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Software.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage1 = new Stage();;
stage1.setScene(new Scene(root1));
stage1.show();
}
public Label lblTest;
public void btnSoftwarePressed(ActionEvent event) {
lblTest.setText("label working");
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(data);
if(data !=null){
System.out.println("data is not null");
tblSoftware.getItems().addAll(data);
}
else System.out.println("data is null");
}
}
FMXL
<?import java.lang.*?>
<?import java .util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="368.0" prefWidth="433.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.SoftwareController">
<children>
<TableView fx:id="tblSoftware" layoutY="102.0" prefHeight="266.0" prefWidth="433.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="102.0">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
<Button fx:id="btnSoftware" layoutY="63.0" mnemonicParsing="false" onAction="#btnSoftwarePressed" text="Button" />
<Label fx:id="lblTest" layoutX="226.0" layoutY="63.0" prefHeight="26.0" prefWidth="130.0" text="22" />
</children>
</AnchorPane>
I wanted to try and see how it worked without using a separate controller and application file.
Note that I had to call buildData in the initialize method like James_D mentioned.
This expects you have H2 as a library somewhere but I think your connection was ok, so just delete the H2 part and uncomment your connection.
package fxml;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ResourceBundle;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class SoftwareController extends Application implements Initializable {
private static ObservableList<ObservableList<String>> data;
#FXML public TableView<ObservableList<String>> tblSoftware;
public void buildData() throws Exception{
data = FXCollections.observableArrayList();
DriverManager.registerDriver(new org.h2.Driver());
Connection conn = DriverManager.getConnection("jdbc:h2:mem:");
Statement stmt = conn.createStatement();
String sql = "CREATE TABLE Data_CMDB_Main (name VARCHAR(255), address VARCHAR(255))";
stmt.executeUpdate(sql);
for (int i = 0; i < 10; i++) {
sql = "INSERT INTO Data_CMDB_Main VALUES ("
+ "'1st string in row " + i + "','2nd string in row " + i + "')";
stmt.executeUpdate(sql);
}
// Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
// String url = "jdbc:sqlserver://IP;databaseName=CMDB";
// Connection conn = DriverManager.getConnection(url, "cmdbadmin", "cmdbadmin!1");
// System.out.print("connection successfulltttt");
String SQL = "SELECT * from Data_CMDB_Main";
ResultSet rs = conn.createStatement().executeQuery(SQL);
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn<ObservableList<String>, String> col = new TableColumn(rs.getMetaData().getColumnName(i+1));
col.setCellValueFactory(p -> new ReadOnlyStringWrapper(p.getValue().get(j)));
tblSoftware.getColumns().add(col);
}
while (rs.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
row.add(rs.getString(i));
}
data.add(row);
}
tblSoftware.setItems(data);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Software.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
stage.setScene(new Scene(root1));
stage.show();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
buildData();
} catch (Exception ex) {
ex.printStackTrace();
}
}
#FXML private void btnSoftwarePressed(){}
public static void main(String[] args) { launch(args);}
}