I am working on a java/Javafx project for the first time and i have a TableView with multiple column (name, prename, age...) to present my data and I need the user to be able to select a single row and give me everytime all anformation about the person(Other columns) even when he click at another column but I haven't been able to find the right way to do it.
When i select a row my code give everytime the value of the cell i click on, but i need other informations to search with in my SQLite data base and work on it (Delete/edit this person..)
Here is the code that i use:
...//rest of code
#Override
public void initialize(URL location, ResourceBundle resources) {
private TableView<Student> tbl_elev=new TableView<Student>();
...
tbl_elev.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observableValue, Object oldValue, Object newValue) {
//Check whether item is selected and set value of selected item to Label
if (tbl_elev.getSelectionModel().getSelectedItem() != null) {
TableViewSelectionModel<Student> selectionModel = tbl_elev.getSelectionModel();
ObservableList<?> selectedCells = selectionModel.getSelectedCells();
#SuppressWarnings("unchecked")
TablePosition<Object, ?> tablePosition = (TablePosition<Object, ?>) selectedCells.get(0);
Object val = tablePosition.getTableColumn().getCellData(newValue);
System.out.println("Selected Value " + val);
}
}
});
}
... //rest of code
I am waiting for your suggestions and ideas, i dont mind if you suggest another approach because this may be uncompatible (taken from internet) Please if you need any other part of the code just comment, i don't put it all because it is too long to read.. (Sorry of my bad english)
If you specify that the ChangeListener parameters are of type Student you can get use the instance methods from that object:
Here's a Minimal, Complete, and Verifiable example:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SSCCE extends Application {
#Override
public void start(Stage stage) {
VBox root = new VBox();
TableView<Student> studentsTable = new TableView<Student>();
HBox studentBox = new HBox();
Label studentHeader = new Label("Student: ");
Label studentInfo = new Label("");
studentBox.getChildren().addAll(studentHeader, studentInfo);
root.getChildren().addAll(studentsTable, studentBox);
// Prepare the columns
TableColumn<Student, String> firstNameCol = new TableColumn<Student, String>(
"First name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue()
.firstNameProperty());
TableColumn<Student, String> lastNameCol = new TableColumn<Student, String>(
"Last name");
lastNameCol.setCellValueFactory(cellData -> cellData.getValue()
.lastNameProperty());
studentsTable.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<Student>() {
// Here's the key part. See how I specify that the
// parameters are of type student. Now you can use the
// instance methods from Student.
#Override
public void changed(
ObservableValue<? extends Student> observable,
Student oldValue, Student newValue ) {
studentInfo.setText(newValue.getFirstName() + " "
+ newValue.getLastName());
// If you want to get the value of a selected student cell at
// anytime, even if it hasn't changed. Just do e.g.
// studentsTable.getSelectionModel().getSelectedItem().getFirstName()
}
});
studentsTable.getColumns().setAll(firstNameCol, lastNameCol);
// Some mock Student objects
Student student1 = new Student("Eric", "Smith");
Student student2 = new Student("Brad", "Jones");
Student student3 = new Student("Logan", "Thorpe");
// Fill the table with students.
studentsTable.getItems().addAll(student1, student2, student3);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// The student class. In this case an inner class to simplify the example. But generally you should never use inner classes.
class Student {
private StringProperty firstName;
private StringProperty lastName;
public Student(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
}
}
After too many failed attempts and thanks to #Jonatan 's answer the code after i compelete some missing words should be like this:
...//rest of code
#Override
public void initialize(URL location, ResourceBundle resources) {
private TableView<Student> tbl_elev=new TableView<Student>();
...
tbl_elev.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Student>() {
// Here's the key part. See how I specify that the
// parameters are of type student. Now you can use the
// instance methods from Student.
#Override
public void changed(ObservableValue<? extends Student> observable,Student oldValue, Student newValue){
if(newValue!=null){
System.out.println(newValue.getName() + " "+ newValue.getPrename()+" "+newValue.getNaiss());
}
//you can add any other value from Student class via getter(getAdr,getMail,...)
}
});
}
... //rest of code
Output example:
Jonatan stenbacka 2015-09-11
Those value are ready for use to fetch the data base and specify the needed row in it to work on.
Hope that this help someone one day.
thanks...
Related
This is a followup to a prior question I posted here.
In the MCVE below, I have a TableView displaying a list of Person objects. Above the list, I have a single TextField which I use to filter the listed items in the TableView.
The Person class contains 4 fields, but I have my search field only checking for matches in 3 of them: userId, lastName, and emailAddress.
The filtering function works as expected.
However, I now need to rank the results based on which fields were matched and the user Type.
MCVE CODE
Person.java:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public final class Person {
private StringProperty userType = new SimpleStringProperty();
private IntegerProperty userId = new SimpleIntegerProperty();
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty emailAddress = new SimpleStringProperty();
public Person(String type, int id, String firstName, String lastName, String emailAddress) {
this.userType.set(type);
this.userId.set(id);
this.firstName.set(firstName);
this.lastName.set(lastName);
this.emailAddress.set(emailAddress);
}
public String getUserType() {
return userType.get();
}
public void setUserType(String userType) {
this.userType.set(userType);
}
public StringProperty userTypeProperty() {
return userType;
}
public int getUserId() {
return userId.get();
}
public void setUserId(int userId) {
this.userId.set(userId);
}
public IntegerProperty userIdProperty() {
return userId;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getEmailAddress() {
return emailAddress.get();
}
public void setEmailAddress(String emailAddress) {
this.emailAddress.set(emailAddress);
}
public StringProperty emailAddressProperty() {
return emailAddress;
}
}
Main.java:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
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.VBox;
import javafx.stage.Stage;
import java.util.Comparator;
public class Main extends Application {
TableView<Person> tableView;
private TextField txtSearch;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Create the TableView of data
tableView = new TableView<>();
TableColumn<Person, Integer> colId = new TableColumn<>("ID");
TableColumn<Person, String> colFirstName = new TableColumn<>("First Name");
TableColumn<Person, String> colLastName = new TableColumn<>("Last Name");
TableColumn<Person, String> colEmailAddress = new TableColumn<>("Email Address");
// Set the ValueFactories
colId.setCellValueFactory(new PropertyValueFactory<>("userId"));
colFirstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
colLastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));
colEmailAddress.setCellValueFactory(new PropertyValueFactory<>("emailAddress"));
// Add columns to the TableView
tableView.getColumns().addAll(colId, colFirstName, colLastName, colEmailAddress);
// Create the filter/search TextField
txtSearch = new TextField();
txtSearch.setPromptText("Search ...");
addSearchFilter(getPersons());
// Add the controls to the layout
root.getChildren().addAll(txtSearch, tableView);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
private void addSearchFilter(ObservableList<Person> list) {
FilteredList<Person> filteredList = new FilteredList<Person>(list);
txtSearch.textProperty().addListener(((observable, oldValue, newValue) ->
filteredList.setPredicate(person -> {
// Clear any currently-selected item from the TableView
tableView.getSelectionModel().clearSelection();
// If search field is empty, show everything
if (newValue == null || newValue.trim().isEmpty()) {
return true;
}
// Grab the trimmed search string
String query = newValue.trim().toLowerCase();
// Convert the query to an array of individual search terms
String[] keywords = query.split("[\\s]+");
// Create a single string containing all the data we will match against
// BONUS QUESTION: Is there a better way to do this?
String matchString =
String.valueOf(person.getUserId())
+ person.getLastName().toLowerCase()
+ person.getEmailAddress().toLowerCase();
// Check if ALL the keywords exist in the matchString; if any are absent, return false;
for (String keyword : keywords) {
if (!matchString.contains(keyword)) return false;
}
// All entered keywords exist in this Person's searchable fields
return true;
})));
SortedList<Person> sortedList = new SortedList<>(filteredList);
// Create the Comparator to allow ranking of search results
Comparator<Person> comparator = new Comparator<Person>() {
#Override
public int compare(Person person, Person t1) {
return 0;
}
};
// Set the comparator and bind list to the TableView
sortedList.setComparator(comparator);
tableView.setItems(sortedList);
}
private ObservableList<Person> getPersons() {
ObservableList<Person> personList = FXCollections.observableArrayList();
personList.add(new Person("DECEASED", 123, "Chrissie", "Watkins", "fishfood#email.com"));
personList.add(new Person("VET", 342, "Matt", "Hooper", "m.hooper#noaa.gov"));
personList.add(new Person("VET", 526, "Martin", "Brody", "chiefofpolice#amity.gov"));
personList.add(new Person("NEW", 817, "Larry", "Vaughn", "lvaughn#amity.gov"));
return personList;
}
}
You'll see I have an empty Comparator in my Main class. This is what I need help with. I have created comparators in the past that are able to sort based on one field (from my previous question):
Comparator<DataItem> byName = new Comparator<DataItem>() {
#Override
public int compare(DataItem o1, DataItem o2) {
String searchKey = txtSearch.getText().toLowerCase();
int item1Score = findScore(o1.getName().toLowerCase(), searchKey);
int item2Score = findScore(o2.getName().toLowerCase(), searchKey);
if (item1Score > item2Score) {
return -1;
}
if (item2Score > item1Score) {
return 1;
}
return 0;
}
private int findScore(String item1Name, String searchKey) {
int sum = 0;
if (item1Name.startsWith(searchKey)) {
sum += 2;
}
if (item1Name.contains(searchKey)) {
sum += 1;
}
return sum;
}
};
I am not sure how to adapt this for multiple fields, though. Specifically, I want to be able to choose which fields should be ranked "higher."
For this example, what I want to accomplish is to sort the list in this order:
userId starts with a keyword
lastName starts with a keyword
emailAddress starts with a keyword
lastName contains a keyword
emailAddress contains a keyword
Within matches any userType = "VET" should be listed first
I am not looking for Google-level algorithms, but just some way to prioritize matches. I am not very familiar with the Comparator class and have a hard time understanding the JavaDocs for it, as it applies to my needs.
There are several posts on StackOverflow that deal with sorting by multiple fields, but all those I've found are comparing Person to Person. Here, I need to compare Person fields to the txtSearch.getText() value.
How would I go about refactoring this Comparator to set up custom sorting of this nature?
Your scoring concept is close, you just need to come up with factors and follow the rules.
So, here's a simple example:
public int score(Item item, String query) {
int score = 0;
if (item.userId().startsWith(query) {
score += 2000;
}
if (item.lastName().startsWith(query) {
score += 200;
} else if (item.lastName().contains(query) {
score += 100;
}
if (item.email().startsWith(query) {
score += 20;
} else if (item.email().contains(query) {
score += 10;
}
if (item.userType().equals("VET")) {
score += 5;
}
return score;
}
So as you can see, I took each of your criteria and turned them in to different digits within the score, and for the distinction within each criteria, I had different values (10 vs 20, for example). Finally I tacked on 5 for the "VET" type.
The assumption is that the scoring rules are not exclusive (i.e. that each rule refines the scoring, rather than stops it), and the the VET types were tie breakers within each criteria, vs to the top of the list. If VET needs to go to the top of the list (i.e. all VETs will be show before all non-VET), you can change the 5 to 10000, giving it it's own order of magnitude.
Now, using decimal numbers is just easy, but you'll run out of magnitudes after 9 (you'll overflow the int) -- you could also use other bases (base 3 in this example), giving you access to more "bits" in the integer. You could use a long, Or you could use a BigDecimal value and have as many criteria as you like.
But the basics are the same.
Once you have the score, just compare the scores of the two values in your comparator.
You can sort for multiple fields by chaining comparators together. If the first comparator declares two objects to be equal you delegate to the next comparator and continue like this until all comparators have been queried or any of them have returned a value other than 0.
Here is an example:
static class Person {
String name;
int age;
int id;
}
Comparator<Person> c3 = (p1, p2) -> {
return Integer.compare(p1.id, p2.id);
};
Comparator<Person> c2 = (p1, p2) -> {
if (p1.name.compareTo(p2.name) == 0) {
return c3.compare(p1, p2);
}
return p1.name.compareTo(p2.name);
};
Comparator<Person> c1 = (p1, p2) -> {
if (Integer.compare(p1.age, p2.age) == 0) {
return c2.compare(p1, p2);
}
return Integer.compare(p1.age, p2.age);
};
The comparators are queried in the sequence of c1 then c2 then c3.
Of course this is an overly simplified example. In production code you should preferably use a cleaner and more OOP oriented solution.
With JavaFX, what is the best way to bind ChoiceBox to properties of a collection?
In example below I try to bind ChoiceBox elements to name of an ObservableList beans. This works fine when items are added/removed but not when the property value name change.
I was hoping there is a clean and simple solution to this but haven't yet found any example of it...
The class ExampleBean2 in deliberately not implemented with properties since that object may correspond to a external model class out of my control.
package com.playground;
import org.controlsfx.control.PropertySheet;
import org.controlsfx.property.BeanPropertyUtils;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class BindingPlayGround extends Application{
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("FXPlayGround");
Parent content = createContentPane();
Scene scene = new Scene(content, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
protected Parent createContentPane() {
ObservableList<BeanExample2> beans = FXCollections.observableArrayList();
ObservableList<PropertySheet> sheets = FXCollections.observableArrayList();
ListView<PropertySheet> listView = new ListView<PropertySheet>(sheets);
Button addBeanButton = new Button("Add Bean");
addBeanButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
BeanExample2 e = new BeanExample2();
e.setName("Name-not-set");
PropertySheet propertySheet = new PropertySheet(BeanPropertyUtils.getProperties(e));
sheets.add(propertySheet);
beans.add(e);
}
});
VBox vBar = new VBox();
vBar.getChildren().add(listView);
vBar.getChildren().add(addBeanButton);
ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
#Override
public Observable[] call(BeanExample2 param) {
return new Observable[]{new SimpleStringProperty(param, "name")};
}
});
Bindings.bindContent(names, beans);
Button addChoiceBoxButton = new Button("Add ChoiceBox");
addChoiceBoxButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ChoiceBox<BeanExample2> choiceBox = new ChoiceBox<BeanExample2>(names);
vBar.getChildren().add(choiceBox);
}
});
vBar.getChildren().add(addChoiceBoxButton);
return vBar;
}
static class BeanExample2 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "BeanExample2{" +
"name='" + name + '\'' +
'}';
}
}
}
Here
ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
#Override
public Observable[] call(BeanExample2 param) {
return new Observable[]{new SimpleStringProperty(param, "name")};
}
});
you're creating a new property to listen to for updates that cannot be referenced except from the value returned by the call method. The only relationship between the BeanExample2 instance and the SimpleStringProperty is that the BeanExample2 instance is used as bean for the property, which has no effect besides being available via the getBean() method of the property. The value of the property is never assigned let alone modified on a change of the BeanExample2 instance.
To properly trigger updates in the ObservableList, you need to make sure the element in the array returned by the above method is actually notified of updates. Usually you add the property to the class itself:
public static class BeanExample2 {
public final String getName() {
return this.name.get();
}
private final StringProperty name = new SimpleStringProperty();
public final void setName(String value) {
this.name.set(value);
}
#Override
public String toString() {
return "BeanExample2{"
+ "name='" + name.get() + '\''
+ '}';
}
public final StringProperty nameProperty() {
return this.name;
}
}
And return an array containing the property from the Callback
ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
#Override
public Observable[] call(BeanExample2 param) {
return new Observable[]{param.nameProperty()};
}
});
Note that currently there seems to be a bug in ChoiceBox that adds entries for every intermediate value to the ChoiceBox.
ComboBox does not have this issue and could be used instead of a ChoiceBox.
Edit
Apparently this is a bug. The report I've made can be found here. As #James_D noted, this is not an issue with the binding, but it is enough to set the text to null after it has been set to a non-null value.
I am having troubles with JavaFX TextFormatter. I want to limit the length of text in a text field to 10 characters, but I find the if the text property was bound to a non-null value, and then is unbound and rebound to a null value, the text formatter throws an exception:
java.lang.IllegalArgumentException: The start must be <= the end
upon calling TextFormatter.Change#getControlNewText, which is weird, because if anything I would have expected a null reference exception.
I attach a simple code for a complete example exhibiting this problem. If there is anything I'm doing wrong please let me know
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Main extends Application {
private Model m;
private int num = 0;
#Override
public void start(Stage primaryStage) throws Exception {
TextField tf = new TextField();
tf.setTextFormatter(new TextFormatter<>(change -> change.getControlNewText().length() > 10 ? null : change));
Button b = new Button("Click!");
b.setOnAction(ev -> {
if (m != null) {
tf.textProperty().unbindBidirectional(m.nameProperty());
}
m = new Model();
if (num % 2 == 0) {
System.out.println("Setting foo");
m.setName("foo");
}
num++;
tf.textProperty().bindBidirectional(m.nameProperty());
}
);
VBox vb = new VBox(tf, b);
primaryStage.setScene(new Scene(vb));
primaryStage.show();
}
public class Model {
private SimpleStringProperty name = new SimpleStringProperty(this, "name");
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
}
public static void main(String[] args) {
launch(args);
}
}
In this code there is a TextField with a TextFormatter rejecting all changed which result in a string of length>10. When the button is clicked a new Model object is created, and it's name property is bound to the TextField's text property - not before the old Model is unbound. The model is alternating between being initialized with "foo" as name, or not being initialized with a name - that is - the name remains null.
Upon first clicking the button you should see the text being changed to "foo", and when next clicking the button the exception is thrown.
This looks like a bug (it seems like the text formatter's filter doesn't properly handle the text being set to null). A possible workaround is to bind the value property of the text formatter, instead of the text property of the text field:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Main extends Application {
private Model m;
private int num = 0;
#Override
public void start(Stage primaryStage) throws Exception {
TextField tf = new TextField();
TextFormatter<String> textFormatter = new TextFormatter<>(
TextFormatter.IDENTITY_STRING_CONVERTER, "", change ->
change.getControlNewText().length() > 10 ? null : change);
tf.setTextFormatter(textFormatter);
Button b = new Button("Click!");
b.setOnAction(ev -> {
if (m != null) {
// tf.textProperty().unbindBidirectional(m.nameProperty());
textFormatter.valueProperty().unbindBidirectional(m.nameProperty());
}
m = new Model();
if (num % 2 == 0) {
System.out.println("Setting foo");
m.setName("foo");
}
num++;
// tf.textProperty().bindBidirectional(m.nameProperty());
textFormatter.valueProperty().bindBidirectional(m.nameProperty());
}
);
VBox vb = new VBox(tf, b);
primaryStage.setScene(new Scene(vb));
primaryStage.show();
}
public class Model {
private SimpleStringProperty name = new SimpleStringProperty(this, "name", "");
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
}
public static void main(String[] args) {
launch(args);
}
}
I get a nullpointerxception when following this tutorial:
Populate a tableview using database in JavaFX .
I modified it to make it simpler and fit my needs:
Instead of Usermaster, I have Person object.
while(rs.next()){
Person per = new Person();
per.ClientID.set(rs.getInt(1));
per.FirstName.set(rs.getString(2));
per.LastName.set(rs.getString(3));
The code stops at per.ClientID.set(rs.getInt(1)); due to nullpointerxception.
If I make system.out.println(rs.getInt(1)) (or any other column), I get the value... But it appears that I can't pass it to my object per.
All Person object vars are SimpleString/IntergerProperty type, as shown in the tutorial.
Can someone help me to identify the mistake I made in coding this?
Thank you
**Answer: need to initialize values.
Now I have no errors, but my table is not populating...
Full code:
a) 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();
}
}
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;
}
}
Controller Class:
package tableview.view;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
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");
}
}
}
ClientID is null. You didn't initialize it.
If it's a property, you should create the proper getter and setters for it and not use the property directly. Besides you should never use 1, 2, etc in the ResultSet's getter. It's better practice to use the column names.
Given a Person class:
public class Person {
private StringProperty firstName;
private StringProperty lastName;
public Person(String firstName, String lastName){
setFirstName(firstName);
setLastName(lastName);
}
//SETTERS
public final void setFirstName(String value) { firstNameProperty().set(value); }
public final void setLastName(String value) { lastNameProperty().set(value); }
//GETTERS
public String getFirstName() { return firstNameProperty().get(); }
public String getLastName() { return lastNameProperty().get(); }
//PROPERTY GETTERS
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
}
I recreated the JavaFX API example on TableView:
public class TestTableViewBuilder extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
);
TableView<Person> table = new TableView<Person>();
table.setItems(data);
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
TableColumn<Person,String> lastNameCol = new TableColumn<Person,String>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
table.getColumns().setAll(firstNameCol, lastNameCol);
Group root = new Group();
root.getChildren().add(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
I've been trying without success to use the TableViewBuilder to recreate the same table. Anyone have an idea how to use JavaFX 2.0 TableViewBuilder to create a TableView with an existing ObservableList?
Here is a sample:
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TableViewBuilderExample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
final ObservableList<?> data = FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson")
);
stage.setScene(
new Scene(
TableViewBuilder.create().items((ObservableList<Object>) data).columns(
TableColumnBuilder.create().text("First Name").cellValueFactory(new PropertyValueFactory("firstName")).build(),
TableColumnBuilder.create().text("Last Name").cellValueFactory(new PropertyValueFactory("lastName")).build()
).build()
)
);
stage.show();
}
}
There are some strange things going on with the generic type usage in the Builders. I would have liked instead to say something like TableViewBuilder<Person>.create(), but TableViewBuilder has a recursive type as a second generic type parameter which must be supplied to it, so I could not get that strategy to work. The code above is next best thing I could come up with, but it still have some strange typing going on with the ObservableList<?> definition of the data and the need to cast the data to an ObservableList<Object> in the Builder.
Based on Sergey's insight for a type parameterization syntax for the builders I was able to create the following builder which will work with a data type of ObservableList<Person>
TableViewBuilder.<Person>create().items(data).columns(
TableColumnBuilder.<Person, String>create()
.text("First Name").cellValueFactory(new PropertyValueFactory("firstName"))
.build(),
TableColumnBuilder.<Person, String>create()
.text("Last Name").cellValueFactory(new PropertyValueFactory("lastName"))
.build()
).build()
After this exercise, I would be even more inclined to checkout the DataFX project if I had to do this kind of stuff a lot . . .
The trick here is in the fact that Builders are created by factories named create, so you have to parametrize them, not the Builder class name itself which only plays namespace role here.
This way:
TableViewBuilder.<Person>create().build();