Is it possible to edit a cell value in a dynamic TableView (dynamic rows and dynamic columns)?
All I found on the internet was some editable TextFields over the cells.
However, I want to edit the value in the table and then update my List with the new data.
I'm using IntelliJ IDEA 13.1.4 , JavaFX Scene Builder 2.0 and the newest JavaFX version.
Here is the code, where I create the dynamic rows and columns:
public List<String[]> jdata = new LinkedList<>(); //Here is the data
private TableView<String[]> sourceTable;
private ObservableList<String[]> srcData;
.
.
.
int clms;
public void showTable(Convert cnv) {
clms = cnv.getColums(); //number of the columns
for (int i = 0; i < clms; i++) {
TableColumn<String[], String> firstNameCol = new TableColumn<>("\tC"+(i+1)+" \t");
firstNameCol.setMinWidth(20);
int index = i ;
firstNameCol.setCellValueFactory(cellData -> {
String[] rowData = cellData.getValue();
if (index >= rowData.length) {
return new ReadOnlyStringWrapper("");
} else {
String cellValue = rowData[index];
return new ReadOnlyStringWrapper(cellValue);
}
});
sourceTable.getColumns().add(firstNameCol);
}
srcData = FXCollections.observableList(jdata);
sourceTable.getItems().addAll(srcData);
}
Just do
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(event -> {
String[] row = event.getRowValue();
row[index] = event.getNewValue();
});
This code will make the firstNameCol column editable. When you click on any cell under this column, you will get a TextField where you can enter value. When you hit enter, the value gets saved in the table.
UPDATE:
Let us say you have created a model class for your Table, and lets assume its name is TestCasesModel, this is how the above code would look.
firstNameCol.setCellFactory(TextFieldTableCell.<TestCasesModel>forTableColumn());
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<TestCasesModel, String>>() {
#Override
public void handle(CellEditEvent<TestCasesModel, String> t) {
((TestCasesModel) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setObjectName(t.getNewValue());
}
}
);
It is always a good practice to work with POJO classes instead of String arrays.
CellEditEvent must be imported like this:
import javafx.scene.control.TableColumn.CellEditEvent;
Related
when I Run the project the table rows are displayed correctly except the rank stars the show outside the table and inside the colonne a text appears as displayed in the image :
public ListTasksForm(Form previous) {
SpanLabel sp = new SpanLabel();
sp.setText(ServiceTask.getInstance().getAllArticles().toString());
ArrayList<Articles> articles = ServiceTask.getInstance().getAllArticles();
Object[][] rows = new Object[articles.size()][];
for (int iter = 0; iter < rows.length; iter++) {
rows[iter] = new Object[]{
articles.get(iter).getName(), articles.get(iter).getDescription(), articles.get(iter).getLabel(), articles.get(iter).getQuantity(),
articles.get(iter).getRating(), add(createStarRankSlider(articles.get(iter).getId_article()))
};
}
TableModel model = new DefaultTableModel(new String[]{"name", "description", "label", "quantity", "rating", "rate"}, rows);
Table table = new Table(model);
add(table);
getToolbar().addMaterialCommandToLeftBar("", FontImage.MATERIAL_ARROW_BACK, e -> previous.showBack());
}
});
and this is the function for the star rank creation
private Slider createStarRankSlider(int id) {
Slider starRank = new Slider();
starRank.setEditable(true);
starRank.setMinValue(0);
starRank.setMaxValue(10);
int fontSize = Display.getInstance().convertToPixels(3);
Font fnt = Font.createTrueTypeFont("Handlee", "Handlee-Regular.ttf").
derive(fontSize, Font.STYLE_PLAIN);
Style s = new Style(0xffff33, 0, fnt, (byte) 0);
Image fullStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
s.setOpacity(100);
s.setFgColor(0);
Image emptyStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
initStarRankStyle(starRank.getSliderEmptySelectedStyle(), emptyStar);
initStarRankStyle(starRank.getSliderEmptyUnselectedStyle(), emptyStar);
initStarRankStyle(starRank.getSliderFullSelectedStyle(), fullStar);
initStarRankStyle(starRank.getSliderFullUnselectedStyle(), fullStar);
starRank.setPreferredSize(new Dimension(fullStar.getWidth() * 5, fullStar.getHeight()));
starRank.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
ServiceTask.getInstance().UpdateRank(id,starRank.getIncrements());
}
});
return starRank;
}
You didn't include the code for the initStarRankStyle but it's pretty obvious what you did here. You just relied on the behavior of the container. Table derives Container so it includes all of its methods e.g. add(Component).
But these methods won't work correctly since a table fetches its data from the model and invokes add internally. So you're logic is conflicting with the table.
You need to derive table and define how you want that data to be rendered. You can do that by overriding the method protected Component createCell(Object value, int row, int column, boolean editable) as explained here.
I've recently encountered interesting problem. I have a table with 1 int and 3 String columns. I have implemented filtering for the table that works perfectly fine except one minor point: Whenever there is at least one filtered result (but less than the amount of visible rows), the int column display nulls as the values for empty rows. If the amount of found matches is, however more than visible number of rows, no null values are added (even with scroll functionality). This is best described with the pictures:
Filter for non-existent value does not display nulls:
The code for filtering:
FilteredList<Word> filteredData = new FilteredList<>(masterData,e->true);
SortedList<Word> sortedData = new SortedList<>(filteredData);
sortedData.comparatorProperty().bind(table.comparatorProperty());
table.setItems(sortedData);
TextField filter = new TextField();
filter.setPromptText("Filter");
filter.textProperty().addListener((observableValue,oldValue,newValue)->{
filteredData.setPredicate((Predicate<? super Word>) word->{
if(word.getAllCz().toLowerCase().contains(newValue.toLowerCase()))return true;
else if(word.getAllEng().toLowerCase().contains(newValue.toLowerCase()))return true;
else if(String.valueOf(word.getUnitNo()).equals(newValue))return true;
else return false;
});
});
CellValue factory:
column.setCellValueFactory(new PropertyValueFactory<>(data));
column.setCellFactory(tc-> {
TableCell<Word, Integer> cell = new TableCell<>();
Text text = new Text();
cell.setGraphic(text);
text.setTextAlignment(TextAlignment.CENTER);
text.setStyle("-fx-fill: -fx-text-background-color;");
text.setFontSmoothingType(FontSmoothingType.LCD);
text.wrappingWidthProperty().bind(column.widthProperty().subtract(5));
text.textProperty().bind(cell.itemProperty().asString());
return cell;
});
If the cell is empty, its item will be null, and itemProperty().asString() will evaluate to a string containing the literal word "null" (similar to passing a null value to a PrintStream). Your binding needs to treat empty cells as special cases:
column.setCellFactory(tc-> {
TableCell<Word, Integer> cell = new TableCell<>();
Text text = new Text();
cell.setGraphic(text);
text.setTextAlignment(TextAlignment.CENTER);
text.setStyle("-fx-fill: -fx-text-background-color;");
text.setFontSmoothingType(FontSmoothingType.LCD);
text.wrappingWidthProperty().bind(column.widthProperty().subtract(5));
text.textProperty().bind(Bindings.createStringBinding(() -> {
if (cell.isEmpty()) {
return null ;
} else {
return cell.getItem().toString();
}
}, cell.emptyProperty(), cell.itemProperty()));
return cell;
});
or you need to override updateItem():
column.setCellFactory(tc-> {
TableCell<Word, Integer> cell = new TableCell<>() {
private Text text = new Text();
{
this.setGraphic(text);
text.setTextAlignment(TextAlignment.CENTER);
text.setStyle("-fx-fill: -fx-text-background-color;");
text.setFontSmoothingType(FontSmoothingType.LCD);
text.wrappingWidthProperty().bind(column.widthProperty().subtract(5));
}
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
text.setText(null);
} else {
text.setText(item.toString());
}
}
};
return cell;
});
I have never used tableview before and I am quite new to with Java and JavaFX. I tried to mirror an example but since the data is coming from a db client created in house I couldn't copy it exactly. Anyway, my data is going into the table but its entering as what looks like a csv data and each column is not getting put into its respective column. Here is a screenshot to clarify my question:
Getting columns here
for (Column col : drs.getColumns()){
TableColumn tblCol = new TableColumn(col.getName());
tblCol.setCellValueFactory(new Callback<CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> p) {
return new SimpleStringProperty(p.getValue().toString());
}
});
table.getColumns().addAll(tblCol);
}
Getting the rows here:
while (drs.hasNextRow()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 0; i < drs.getColumns().length; i++) {
row.add(drs.getNextRow().getItem(i) + "");
}
data.add(row);
}
And here is where I fail at sending the data to the table.
table.setItems(data);
System.out.println("dis is data: " +data);
the data prints like so:
dis is data: [[data, data, data....], [data, data, data....], [data, data, data....]]
It takes each [row] and puts it into each column for each record. I am assuming I need the data to look like:
[[data], [data], [data]....[data]], [[data], [data], [data]....[data]], [[data], [data], [data]....[data]]
} catch(Exception e){
e.printStackTrace();
System.out.println("everything is broken");
}
}
So, I am lost. I don't know what I am doing and I'd appreciate any help that you can offer me.
In TableView all the data in a row is associated with a item. The TableColumn.cellValueFactory is used to select the "part" of a item that should be shown in the column. Therefore you should use it to select the value:
TableView<ObservableList<String>> table = ...
int index = 0;
for (Column col : drs.getColumns()) {
final int columnIndex = index++;
TableColumn<ObservableList<String>, String> tblCol = new TableColumn(col.getName());
tblCol.setCellValueFactory(new Callback<CellDataFeatures<ObservableList<String>, String>, ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> p) {
return Bindings.stringValueAt(p.getValue(), columnIndex);
}
});
table.getColumns().add(tblCol);
}
Here the Bindings.stringValueAt is used to select the element form the ObservableList.
Also you need to use one row per row:
while (drs.hasNextRow()) {
ObservableList<String> row = FXCollections.observableArrayList();
Row sourceRow = drs.getNextRow();
for (int i = 0; i < drs.getColumns().length; i++) {
row.add(Objects.toString(sourceRow.getItem(i)));
}
data.add(row);
}
I have created a table below that has name of the places as its entryPoint in the first column. I want to keep a textfield so that one can search for the place he wants to view in the table.
How can i do this? For eg: if i type "a" in text field, all the places starting from "a" only are shown in the table.
json value for table
connectionRequest = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
results = p.parse(new InputStreamReader(input));
responseInout = (Vector) results.get("inout");
for (int i = 0; i < responseInout.size(); i++) {
Hashtable hash = (Hashtable) responseInout.get(i);
String entryPoint = (String) hash.get("entry_point");
String passengerIn = (String) hash.get("passenger_in");
String passengerOut = (String) hash.get("passenger_out");
String vehicleIn = (String) hash.get("vehicle_in");
String vehicleOut = (String) hash.get("vehicle_out");
dataInOut[i][0] = entryPoint;
dataInOut[i][1] = passengerIn;
dataInOut[i][2] = passengerOut;
dataInOut[i][3] = vehicleIn;
dataInOut[i][4] = vehicleOut;
}
}
connectionRequest.setPost(false);
connectionRequest.setUrl("http://capitaleyedevelopment.com/~admin/traffic/api/reports/getReports/2015-12-30");
connectionRequest.setDuplicateSupported(true);
NetworkManager.getInstance().addToQueueAndWait(connectionRequest);
//table
Table table = new MyTable(new DefaultTableModel(columnNamesInOut, dataInOut));
//what to do here in textField
TextField tf = new TextField();
tf.addDataChangeListener(new DataChangedListener() {
#Override
public void dataChanged(int type, int index) {
String searchPlace = tf.getText();
}
});
You can have two types of "search":
Data narrowing - this will hide the rows where the text doesn't show.
Highlight - this will highlight the cells where the data appears
If you choose the data narrowing route just create a new model without the rows that don't contain the data you want and invoke: table.setModel(searchModel); this will leave only the search results.
If you want the highlighting mode In the search field just call table.setModel(table.getModel()); this will force the table to rebuild.
Then override in the table:
protected Component createCell(Object value, int row, int column, boolean editable) {
Component c = super.createCell(value, row, column, editable);
if(isSearchedValue(value)) {
c.setUIID("SearchResult");
}
return c;
}
Then style SearchResult to be the highlight color you want and all is well...
Since the program is too large I'll just paste the important parts of code. Here's the problem:
I have two JTables. First one collects data from DB and displays the list of all invoices stored in DB. The purpose of the second table is when you click on one row from the table, event handler needs to collect integer from column ID. Using this ID the second table will then display all the contest of that invoice (all the products stored in it).
First and second table work perfectly. The problem is that I have no idea how can I collect certain data (I basically just need ID column) from a selected row and then through a method I already made update the second JTable with new info. Here's my code if it helps:
(PS: once I learn how to do that, will the list on the left change every time by default when I select different row, or do I need to use validate/revalidate methods?)
public JPanel tabInvoices() {
JPanel panel = new JPanel(new MigLayout("", "20 [grow, fill] 10 [grow, fill] 20", "20 [] 10 [] 20"));
/** Labels and buttons **/
JLabel labelInv = new JLabel("List of all invoices");
JLabel labelPro = new JLabel("List of all products in this invoice");
/** TABLE: Invoices **/
String[] tableInvTitle = new String[] {"ID", "Date"};
String[][] tableInvData = null;
DefaultTableModel model1 = new DefaultTableModel(tableInvData, tableInvTitle);
JTable tableInv = null;
/** Disable editing of the cell **/
tableInv = new JTable(model1){
public boolean isCellEditable(int r, int c) {
return false;
}
};
/** Load the invoices from DB **/
List<Invoice> listInv = is.getAllInvoices();
for (int i = 0; i < listInv.size(); i++) {
model1.insertRow(i, new Object[] {
listInv.get(i).getID(),
listInv.get(i).getDate()
});
}
/** TABLE: Invoice Info **/
String[] tableInfTitle = new String[] {"ID", "Name", "Type", "Price", "Quantity"};
String[][] tableInfData = null;
DefaultTableModel model2 = new DefaultTableModel(tableInfData, tableInfTitle);
JTable tableInf = null;
/** Disable editing of the cell **/
tableInf = new JTable(model2){
public boolean isCellEditable(int r, int c) {
return false;
}
};
/** Load the products from DB belonging to this invoice **/
List<Product> listPro = is.getInvoiceInfo(1); // Here's where I need the ID fetched from selected row. For now default is 1.
for (int i = 0; i < listPro.size(); i++) {
model2.insertRow(i, new Object[] {
listPro.get(i).getID(),
listPro.get(i).getName(),
listPro.get(i).getType(),
listPro.get(i).getPrice(),
listPro.get(i).getQuantity()
});
}
/** Scroll Panes **/
JScrollPane scrollInv = new JScrollPane(tableInv);
JScrollPane scrollPro = new JScrollPane(tableInf);
panel.add(labelInv);
panel.add(labelPro, "wrap");
panel.add(scrollInv);
panel.add(scrollPro);
return panel;
}
For now, the right table only displays content of the first invoice:
With the help of following code you can get the value of selected clicked cell, so you just have to click on ID cell value (the Invoicee ID whose Products you want to see in second table) and with the help of following event handler you will get the value and then you can get data based on that ID and set to second table. (In the code below, table is the object of your first table)
(Off-course you will have to apply some validation too, to check that the selected (and clicked) cell is ID not the DATE)
table.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int row = table.rowAtPoint(e.getPoint());
int col = table.columnAtPoint(e.getPoint());
Object selectedObj = table.getValueAt(row, col);
JOptionPane.showMessageDialog(null, "Selected ID is " + selectedObj);
}
});