i am beginner of javafx. i just use way java jtable like load the data but i couldn't load the data to tableview what i tried so far i attached below.how to load the data to tableview
#FXML
private TableColumn<?, ?> table1;
public void table_load()
{
int c;
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost/addressbook", "root","");
pst = con.prepareStatement("select * from records");
ResultSet rs = pst.executeQuery();
ResultSetMetaData rd = rs.getMetaData();
c = rd.getColumnCount();
df = (DefaultTableModel)table1.getCellData(0);
df.setRowCount(0);
while (rs.next())
{
Vector v = new Vector();
for (int i=1; i<=c; i++)
{
v.add(rs.getString("id"));
v.add(rs.getString("name"));
v.add(rs.getString("address"));
v.add(rs.getString("phone"));
}
df.addRow(v);
}
} catch (SQLException ex) {
} catch (ClassNotFoundException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Usually you don't add the data to a TableColumn directly. The usual way would be to have objects of a type you can add. Let's assume your result set would contain objects of type Customer and you could extract one or many customer objects from your result set. Then you would rather work with the following (need to be defined in the fxml file:
A superordinate table like TableView <Customer> customerView
a number of table columns like:
TableColumn<Customer, String> idColumn
TableColumn<Customer, String> nameColumn
TableColumn<Customer, String> addressColumn
TableColumn<Customer, String> phoneColumn
You need to set the value factory for the columns to make clear which attribute is to be displayed.
idColumn.setCellValueFactory(new PropertyValueFactory<>("[ATTRIBUTE NAME IN CUSTOMER CLASS]"));
like
idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));
The last step should be to fill the table view with data:
customerView.setItems(observableCustomers);
where observableCustomers is a list of type ObservableList<Customer>. javafx.collections.FXCollections will help you here to create a corresponding object.
It should be similar to this:
#FXML
TableView <Customer> customerView;
#FXML
TableColumn<Customer, String> idColumn;
#FXML
TableColumn<Customer, String> nameColumn;
#FXML
TableColumn<Customer, String> addressColumn;
#FXML
TableColumn<Customer, String> phoneColumn;
ObservableList<Customer> observableCustomers;
// init method or constructor, whatever suits your needs
private void initTable() {
observableCustomers = FXCollections.observableArrayList();
idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
addressColumn.setCellValueFactory(new PropertyValueFactory<>("address"));
phoneColumn.setCellValueFactory(new PropertyValueFactory<>("phone"));
}
public void table_load()
{
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost/addressbook", "root","");
pst = con.prepareStatement("select * from records");
ResultSet rs = pst.executeQuery();
ResultSetMetaData rd = rs.getMetaData();
// CAST/TRANSFORMATION TO BE DONE BY YOU
List<Customer> customerList = ((List<Customer>) rs.toList());
// only if necessary
observableCustomers.clear();
// add customers
observableCustomers.addAll(customerList);
} catch (SQLException ex) {
} catch (ClassNotFoundException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Unlike in Swing, a JavaFX TableView does not use a separate model. Instead, the data is the model. You pass this data to the TableView using the TableView’s setItems method.
This means you need to create a class to hold your data. Since your table is called records, I would name the data class Record.
If you look at the documentation of JavaFX classes, you should notice a pattern. Bean properties are encapsulated attributes, which consist of three methods:
A read method, which always starts with get followed by a capital letter.¹
A write method, which always starts with set followed by a capital letter.²
A property method, whose name always ends with Property.
For instance, consider the javafx.stage.Window class. It has an “opacity” property, which is represented by these methods:
double getOpacity()
void setOpacity(double)
DoubleProperty opacityProperty()
Your data class needs to follow the same pattern:
public class Record {
private final StringProperty id;
private final StringProperty name;
private final StringProperty address;
private final StringProperty phone;
public Record() {
id = new SimpleStringProperty(this, "id");
name = new SimpleStringProperty(this, "name");
address = new SimpleStringProperty(this, "address");
phone = new SimpleStringProperty(this, "phone");
}
public StringProperty idProperty() { return id; }
public String getId() { return id.get(); }
public void setId(String newId) { id.set(newId); }
public StringProperty nameProperty() { return name; }
public String getName() { return name.get(); }
public void setName(String newName) { name.set(newName); }
public StringProperty addressProperty() { return address; }
public String getAddress() { return address.get(); }
public void setAddress(String newAddress) { address.set(newAddress); }
public StringProperty phoneProperty() { return phone; }
public String getPhone() { return phone.get(); }
public void setPhone(String newPhone) { phone.set(newPhone); }
#Override
public String toString() {
return String.format("%s[id=%s, name=%s]",
getClass().getName(), getId(), getName());
}
}
Creating these objects is straightforward:
#FXML
private TableView<Record> table;
#FXML
private TableColumn<Record, String> idColumn;
#FXML
private TableColumn<Record, String> nameColumn;
#FXML
private TableColumn<Record, String> addressColumn;
#FXML
private TableColumn<Record, String> phoneColumn;
// ...
ObservableList<Record> records = FXCollections.observableArrayList();
try (ResultSet rs = pst.executeQuery()) {
while (rs.next())
{
Record record = new Record();
record.setId(rs.getString("id"));
record.setName(rs.getString("name"));
record.setAddress(rs.getString("address"));
record.setPhone(rs.getString("phone"));
records.add(record);
}
}
table.setItems(records);
Instead of a table model, you must instead tell each table column which data it should display:
idColumn.setCellValueFactory(f -> f.getValue().idProperty());
nameColumn.setCellValueFactory(f -> f.getValue().nameProperty());
addressColumn.setCellValueFactory(f -> f.getValue().addressProperty());
phoneColumn.setCellValueFactory(f -> f.getValue().phoneProperty());
¹ Read methods whose return type is primitive boolean may use is instead of get.
² Write methods are not needed for read-only properties.
Related
I'm creating an app using the mvvm pattern with android room, but I've ran into some trouble validating user input. When a user wants to add an ingredient to the app, they are required to enter a name for this ingredient. I want the app to notify the user if the name is already in use. I have tried some stuff using the Transformations.Map() functions but without any success.
I'm fairly new to the mvvm pattern and LiveData, and I've been stuck on this for quite a while now so any advice would be appreciated.
This is the ingredient entity:
#Entity(tableName = "ingredient")
public class BaseIngredient {
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
#ColumnInfo(name = "cooking_time")
private int cookingTime;
#Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, #NonNull String name, #NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
This is the IngredientRepository I'm using:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
The IngredientDao:
#Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
#Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
#Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
#Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
#Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
And finally the ViewModel that is used to create an Ingredient
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(#NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
The onXXEntered() functions in the viewmodel are linked to the textViews in the fragment, and the saveIngredient() function is called when a save button is pressed. The XXError LiveData's are used to display errors to the user.
The real problem lies in the fact that LiveData is async, and the user can change their input and click the save button before the LiveData contains the result from the database. If I want the check the input upon saving it, the 'add activity' will have already finished before the check is done.
Again, any help would be very much appreciated.
I had to do something similar in one of my recent projects. What I did was:
Room cannot create columns with SQLite Unique constraint (if it is not the PrimaryKey - which is your case). So don't initialize the database in your app code using Room. Instead, create a database file outside your application. Add a Unique constraint on the 'name' column. Then add the database file in your project under assets folder. (for example, create a subfolder inside assets - 'db_files' - and copy your pre-created database file under this folder)
I guess you use Singleton pattern for your #DataBase class. Replace your 'getInstance()' method with following:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
This creates a copy of your pre-packaged database file under applications database files path.
With unique constraint in place, your #Insert and #Update annotated methods will respect the constraint condition, and will throw a SQLiteConstraintException if you try to insert a previously used name. You can catch this exception, and pass it to your View or ViewModel as you wish (I implemented a simple centralized event publisher component).
I hope that helps.
I am trying to retrieve images from mysql database using javafx. Images are stored in database using long blob. garbage values appear when doing so . this is the code used to retrieve image from database.
JFXTreeTableColumn<Property,String> propertyImage = new JFXTreeTableColumn<>("Image");
propertyImage.setPrefWidth(100);
propertyImage.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<Property, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<Property, String> param) {
return param.getValue().getValue().propertyImage;
}
});
the property view
Database connection
public class Property extends RecursiveTreeObject<Property>{
StringProperty propertyId;
StringProperty address;
StringProperty state;
StringProperty zipCode;
StringProperty price;
StringProperty bedNum;
StringProperty bathNum;
StringProperty propertyImage;
StringProperty propertyType;
StringProperty propertyStatus;
StringProperty squareFeet;
StringProperty parkingSpot;
StringProperty agent_id;
public Property(){
super();
}
public Property(String propertyId, String address, String state, String zipCode, String price, String bedNum, String bathNum, String propertyImage, String propertyType, String propertyStatus, String squareFeet, String parkingSpot, String agent_id) {
this.propertyId = new SimpleStringProperty(propertyId);
this.address = new SimpleStringProperty(address);
this.state = new SimpleStringProperty(state);
this.zipCode = new SimpleStringProperty(zipCode);
this.price = new SimpleStringProperty(price);
this.bedNum = new SimpleStringProperty(bedNum);
this.bathNum = new SimpleStringProperty(bathNum);
this.propertyImage = new SimpleStringProperty(propertyImage);
this.propertyType = new SimpleStringProperty(propertyType);
this.propertyStatus = new SimpleStringProperty(propertyStatus);
this.squareFeet = new SimpleStringProperty(squareFeet);
this.parkingSpot = new SimpleStringProperty(parkingSpot);
this.agent_id = new SimpleStringProperty(agent_id);
}
}
DB connection
ObservableList<Property> properties = FXCollections.observableArrayList();
Connection connection = DBConnection.getConnection();
try {
PreparedStatement ps = (PreparedStatement)connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while(rs.next()){
properties.add(new Property(rs.getInt(1)+"",rs.getString(2),rs.getString(3),rs.getString(4),rs.getString(5),rs.getString(6),rs.getString(7),rs.getString(8),rs.getString(9),rs.getString(10),rs.getString(11),rs.getString(12),rs.getString(13)));
}
} catch (SQLException ex) {
Logger.getLogger(SearchSalePropertyController.class.getName()).log(Level.SEVERE, null, ex);
}
Finally, you posted the codes for database access. You are already doing it wrong when you were getting the data from the ResultSet.
while(rs.next()) {
InputStream in = new rs.getBinaryStream(8);
Image image = getImageFromStream(in); // You need to convert this back into a JavaFX Image instance,
// which depends on the original Image, e.g. file format etc.
// It could be as simply as new Image(in) using the JavaFX Image constructor
properties.add(new Property(
rs.getInt(1) + "",
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getString(5),
rs.getString(6),
rs.getString(7),
image,
rs.getString(9),
rs.getString(10),
rs.getString(11),
rs.getString(12),
rs.getString(13)
));
}
Of course, Property.propertyImage should also be changed to ObjectProperty<Image>. Similarly the column should also be changed to JFXTreeTableColumn<Property, Image>.
I am trying to write a simple application that retrieves data from DB and writes back to DB. I use javafx, I am trying to list my data in a tableview and be able to edit into it. But could not make editable option work. Please see below for the error explanation. It gives error here.
id.setCellFactory(TextFieldTableCell.<Person>forTableColumn());//It gives error here.
public class Person {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class PersonController
{
#FXML
private TableView<Person> personGrid;
private ObservableList<Person> dataPerson;
dataPerson = FXCollections.observableArrayList();
Person person;
try {
statement = dbConnection.connectToDB().prepareStatement(
"SELECT ID FROM PERSON");
ResultSet queryResult = statement.executeQuery();
while (queryResult.next()) {
person = new Person();
person.setId(queryResult.getInt("id"));
dataPerson.add(person);
}
personGrid.setItems(dataPerson);
personGrid.setEditable(true);
TableColumn<Person, Integer> id = new TableColumn("ID");
id.setCellValueFactory(new PropertyValueFactory("id"));
id.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
// Here I get the error javafx.util.Callback<javafx.scene.control.TableColumn<org.core.Person, java.lang.Integer>,javafx.scene.control.TableCell<org.core.Person, java.lang.Integer>>
in TableColumn cannot be applied to javafx.util.Callback<javafx.scene.control.TableColumn<org.core.Person, java.lang.String>,javafx.scene.control.TableCell<org.core.Person, java.lang.String>>. As its seen my field type is int I do not understand why it keeps saying String.
//Also I tried it this way. It does not give error msg. But cannot edit into cell when I double click.
TableColumn<Person, Integer> id = new TableColumn("ID");
id.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> p) {
return new SimpleIntegerProperty( p.getValue().getId()).asObject();
}
});
id.setOnEditCommit(
(TableColumn.CellEditEvent<Person, Integer> cell) -> {
((Person) cell.getTableView().getItems().get(
cell.getTablePosition().getRow())
).setId(cell.getNewValue());
});
personGrid.getColumns().addAll(id);
}
I have a combobox in my application that selects data fetched from a database but the problem is i need to make that combobox select multiple values .
I looked into this question on stackoverflow
Selecting multiple items from combobox
Taking into Consideration the comments i have changed the app to use java8 and checkedCombobox of ControlsFX.
But how to acheive the keyvalue pair association in this combobox is it possible.
As we could use setCellfactory in normal combobox.
My checked ComboBox
Class
/*
Added for Application Lov
*/
package businesstier;
/**
*
* #author rahul_singh41
*/
public class KeyValuePair {
private final String key;
private final String value;
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public KeyValuePair(String key, String value) {
this.key = key;
this.value = value;
}
#Override
public String toString() {
return key;
}
}
Method
public static ObservableList<KeyValuePair> getKeyValue(Connection conFrom) throws SQLException{
ObservableList<KeyValuePair> data = FXCollections.observableArrayList();
//data.add(new KeyValuePair("Select Application Name", null));
KeyValuePair keyValuePair;
String query = "select application_name,application_id from apps.fnd_application_vl";
PreparedStatement ps = null;
ResultSet rs = null;
ps = conFrom.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()){
//System.out.println("Key"+rs.getString(1)+"value"+rs.getString(2));
keyValuePair = new KeyValuePair(rs.getString(1), rs.getString(2));
data.add(keyValuePair);
}
//System.out.println("KeyValue pair"+data);
return data;
}
#FXML
private CheckComboBox<KeyValuePair> applicationCombo = new CheckComboBox<KeyValuePair>();
applicationCombo.getItems().addAll((AOLMethods.getKeyValue(connFrom)));
I am trying to set the Parent List in a ParameterizedRowMapper how is this written or approached. I have two Objects one for parent and one for children however children contains a ListThe parents for each child are stored in a separate table in the database and the mapping is 1 - many.
The select for the records for the parents will be done in a separate ResultSet. Will the mapping have to be done separately (separate ParameterizedRowMapper), if so how will i have to write the ParameterizedRowMapper this is the major concern how ParameterizedRowMapper is written to accommodate a list items.
ParameterizedRowMapper
public static class ChildrenMapper implements ParameterizedRowMapper<Children>{
public Children mapRow(ResultSet rs, int rowNum) throws SQLException {
Children child = new Children();
child.setFirstName(rs.getString("firstName"));
child.setLastName(rs.getString("lastName"));
//a child can have many Parents or gaurdians
child.setParent(List<Parent>);
return child;
}
}
Based on my research i have found that i need to use ResultSetExtractor, however i have a questions on the use of that. Do i integrate it into the class at the point of setting the Parent? Can someone guide me on how it can be done the correct way
Children.java
Public class Children(){
int cid;
String firstName;
String lastName;
List<Parent>parents;
..
//getters/setters
}
Parent.java
Public class Parent(){
int pid;
String firstName;
String lastName;
..
//setters/getters
}
I will show how to do this for a canonical 1-to-many example, you can adapt it to your vo class / table.
Order class
public class Order {
private Long orderId;
private String user;
private List<LineItem> items;
// Getter / setter omitted
}
Item class
public class LineItem {
private Long lineItemId;
private String product;
private int quantity;
// Getter / setter omitted
}
Use two rowmappers one for each class and then use a result set extractor to convert multiple rows into one order + line items
OrderRepository
public final static RowMapper<Order> orderMapper = ParameterizedBeanPropertyRowMapper.newInstance(Order.class);
public final static RowMapper<LineItem> lineItemMapper = ParameterizedBeanPropertyRowMapper.newInstance(LineItem.class);
public Order findOrderWithItems(Long orderId) {
return jdbcTemplate.query("select * from orders, line_item "
+ " where orders.order_id = line_item.order_id and orders.order_id = ?",
new ResultSetExtractor<Order>() {
public Order extractData(ResultSet rs) throws SQLException, DataAccessException {
Order order = null;
int row = 0;
while (rs.next()) {
if (order == null) {
order = orderMapper.mapRow(rs, row);
}
order.addItem(lineItemMapper.mapRow(rs, row));
row++;
}
return order;
}
}, orderId);
}
public List<Order> findAllOrderWithItmes() {
return jdbcTemplate.query("select * from orders, line_item "
+ " where orders.order_id = line_item.order_id order by orders.order_id",
new ResultSetExtractor<List<Order>>() {
public List<Order> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<Order> orders = new ArrayList<Order>();
Long orderId = null;
Order currentOrder = null;
int orderIdx = 0;
int itemIdx = 0;
while (rs.next()) {
// first row or when order changes
if (currentOrder == null || !orderId.equals(rs.getLong("order_id"))) {
orderId = rs.getLong("order_id");
currentOrder = orderMapper.mapRow(rs, orderIdx++);
itemIdx = 0;
orders.add(currentOrder);
}
currentOrder.addItem(lineItemMapper.mapRow(rs, itemIdx++));
}
return orders;
}
});
}