Fixed cell size table with Java-FX - java

I've been fighting with this for some time, and finally figured I needed some help.
I am trying to create a table where each cell displays a particular color. This is dynamically created based on a user selected image.
I got it to work with a TilePane, but because of the size of the table, it was running extremely slow.
Currently, I am trying to make it work using a TableView. The TableView is being created, but not populated. My code to create the TableView, and my custom TableCell class, is below.
My main question is this: Is this the best way to go about this? If so, what am I doing wrong?
Thank you in advance for the awesome assistance I always see here.
Code to create TableView:
private ObservableList<ObservableList<Stitch>> stitchList;
private TableView<Stitch> pattern;
#Override
protected TableView<Stitch> call() throws Exception {
for (int i=0; i< stitchList.size(); i++) {
TableColumn<Stitch, Color> column = new TableColumn<>();
column.setCellValueFactory(new Callback<CellDataFeatures<Stitch, Color>, ObservableValue<Color>>() {
public ObservableValue<Color> call(CellDataFeatures<Stitch, Color> stitch) {
return stitch.getValue().getDisplayColorProperty();
}
});
column.setCellFactory(new Callback<TableColumn<Stitch, Color>, TableCell<Stitch, Color>>() {
#Override public TableCell<Stitch, Color> call(TableColumn<Stitch, Color> list) {
return new StitchCell();
}
});
//Set column sizes
column.setMinWidth(10);
column.setMaxWidth(10);
pattern.getColumns().add(column);
}
return pattern;
} // End Call
Code for custom cell class
public class StitchCell extends TableCell<Stitch, Color> {
#Override
protected void updateItem(Color color, boolean empty) {
super.updateItem(color, empty);
if (empty || color == null) {
this.setStyle("-fx-background-color: white");
} else {
this.setStyle("-fx-background-color: #" + Integer.toHexString(color.hashCode()));
}
}
}

When you create a TableView<S>, S is the type of the object displayed in each row of the table. The items property of the table, which is an ObservableList<S> contains the collection of items, each one of which is displayed in a table row.
If your data is in the form ObservableList<ObservableList<Stitch>> stitchList, then you need a TableView<ObservableList<Stitch>>. (In other words, S is ObservableList<Stitch>.) Each element of stitchList, which is an ObservableList<Stitch> represents a row in the table.
TableView is not designed with ObservableList<ObservableList> as the primary use case for representing data, so you need to do a little bit of work to get the cellValueFactory to work right. If you are certain that every row has the same number of items, you can perhaps minimize this a bit, but you need something like the following:
TableView<ObservableList<Stitch>> pattern = new TableView<>();
ObservableList<ObservableList<Stitch>> stitchList = ... ;
// Set items (i.e. rows) in table:
pattern.setItems(stitchList);
// iterate through all rows:
for (ObservableList<Stitch> row : pattern) {
// if this row contains more elements than columns we have already created,
// (must be true on first row, may be true on subsequent rows if data not rectangular)
// create a new column for each additional element
for (int i = pattern.getColumns().size(); i < row.size() ; i++) {
TableColumn<ObservableList<Stitch>, Color> column = new TableColumn<>();
final int columnIndex = i ;
column.setCellValueFactory(new Callback<CellDataFeatures<ObservableList<Stitch>, Color>, ObservableValue<Color>>() {
#Override
public ObservableValue<Stitch> call(CellDataFeatures<ObservableList<Stitch>, Stitch>> stitch) {
return stitch.getValue() // the row value, i.e. an ObservableList<Stitch>
.get(columnIndex) // the Stitch for this cell
.getDisplayColorProperty();
}
});
column.setCellValueFactory(new Callback<TableColumn<ObservableList<Stitch>, Color>, TableCell<ObservableList<Stitch>, Color>>() {
#Override
public TableCell<ObservableList<Stitch>, Color> call(TableColumn<ObservableList<Stitch>, Color> col) {
return new StitchCell();
}
});
column.setMinWidth(10);
column.setMaxWidth(10);
pattern.getColumns().add(column);
}
}
If you are using Java 8, you can replace the Callbacks with lambda expressions, and get rid of the ugliest parts of the code:
for (ObservableList<Stitch> row : pattern) {
// if this row contains more elements than columns we have already created,
// (must be true on first row, may be true on subsequent rows if data not rectangular)
// create a new column for each additional element
for (int i = pattern.getColumns().size(); i < row.size() ; i++) {
TableColumn<ObservableList<Stitch>, Color> column = new TableColumn<>();
final int columnIndex = i ;
column.setCellValueFactory( rowData ->
rowData.getValue() // the row value, i.e. an ObservableList<Stitch>
.get(columnIndex) // the Stitch for this cell
.getDisplayColorProperty() );
column.setCellValueFactory(col -> new StitchCell());
column.setMinWidth(10);
column.setMaxWidth(10);
pattern.getColumns().add(column);
}
}

Related

Filter a tableView leaves some data in a specific column

I have made my table view with column and everything works fine.
just one thing that I have remarked is that when I filter rows, some data stay in a column that I have defined its cellFactory.
Image showing the problem:
code of the column:
#FXML
private TableColumn<Log, Integer> colQuanJour;
colQuanJour.setCellValueFactory(new PropertyValueFactory<>("quantite"));
CellFactory to add color and character (+ or -)
colQuanJour.setCellFactory(
new Callback<TableColumn<Log,Integer>,TableCell<Log,Integer>>(){
#Override
public TableCell<Log, Integer> call(TableColumn<Log, Integer> param) {
return new TableCell<Log, Integer>(){
#Override
protected void updateItem(Integer item, boolean empty) {
if( ! empty){
int currIndex = indexProperty().getValue() < 0 ? 0
: indexProperty().getValue();
int operIndex = param.getTableView().getItems().get(currIndex).getOperation().getId_operation();
setStyle("-fx-font-weight: bold");
String Char ;
if(operIndex == 2){
setStyle("-fx-font-size : 16px");
setStyle("-fx-text-fill : red");
Char = "-";
}else {
setStyle("-fx-font-size : 16px");
setStyle("-fx-text-fill : green");
Char = "+";
}
String val = String.valueOf(param.getTableView().getItems().get(currIndex).getQuantite());
setText(Char+val);
}
}
};
}
});
Log is a pojo that contains a property quantity (SimpleIntegerProperty).
and my table is
TableView<Log> tblart;
function to filter:
this function takes the value from the comboBox to choose which column to use to filter, by default it filter from all the table as shown below:
void filterOneJour(String id,String newVal){
ObservableList<Log> obsList = FXCollections.observableArrayList();
for(Log jour : tblJour.getItems()){
String num = String.valueOf(jour.getNum());
String qte = String.valueOf(jour.
switch(id){
case "Numéro" :
if(num.toUpperCase().contains(newVal))
obsList.add(jour);break;
case "Quantité":
if(qte.toUpperCase().contains(newVal))
obsList.add(jour);break;
default :
if(num.toUpperCase().contains( qte.toUpperCase().contains(newVal) )
obsList.add(jour);
}
}
tblJour.setItems(obsList);
}
So I hope that Everything is clear, I think the problem come from cellFactory and more exactly the updateItem.
Yes as you mentioned the problem is from updateItem method. TableView (acutally VirtualFlow) is rendered by using same rows/cells to avoid performance issues. The row/cell updateItem will be called based on the provided item. In your case, you only added to update the cell if the item is present and ignored it when it is empty. So the cell will not get updated for empty rows.
To fix your issue.. you need to add the else condition.
colQuanJour.setCellFactory(
new Callback<TableColumn<Log,Integer>,TableCell<Log,Integer>>(){
#Override
public TableCell<Log, Integer> call(TableColumn<Log, Integer> param) {
return new TableCell<Log, Integer>(){
#Override
protected void updateItem(Integer item, boolean empty) {
if( ! empty){
// Your code...
setText(Char+val);
}else{
setText(null); // This will clear the text for empty rows
}
}
};
}
});

How to change tableView row color when some conditions are valid (javaFX)

My application contains:
An arraylist of integers.
this list update every few second (numbers are added and other are deleted)
Tableview<Data>
the Data contains several fields and semNumId field (which is integer)
I want to be able to paint the rows, which the arraylist contains the semNumId numbers with green colour.
the arraylist may be change (and the tableview data will contain the same data). so the green colour may be set on and off without changing the table data
I have look here:
JavaFx How to set row background color of specifics rows in TableView
but seems it doesn't help much.
How can I implement this ?
pay attention that:
tableView.setRowFactory(tv -> {
TableRow<Data> row = new TableRow<>();
create new tableRow which I cant check if it's semNumId in the list (because when creating with new operation the default is 0);
Please add example code
Not sure if I understood you correctly...
private final ObservableList<Integer> numbers = FXCollections.observableArrayList<>();
private final BooleanProperty numbersChanged = new SimpleBooleanProperty(false);
numbers.addListener(new ListChangeListener<Number> {
#Override public void onChanged(Change<? extends Number> c) {
numbersChanged.set(true);
}
});
tableView.setRowFactory(tv -> {
TableRow<Data> row = new TableRow<>();
BooleanBinding contains = Bindings.createBooleanBinding(() -> {
if (numberChanged.get()) {
if (!numbers.isEmpty() && row.getItem() != null && numbers.contains(row.getItem().getSemNumId())) {
return true;
}
numberChanged.set(false);
}
return false;
}, row.itemProperty(), numbersChanged);
row.styleProperty().bind(Bindings.when(contains)
.then("-fx-background-color: green;")
.otherwise(""));
return row;
});
This implementation assumes that your Data object will not change its value (semNumId) at runtime (i.e. immutable).

Jtable column render

I've got a table with three columns. Columns are added to the table using these few lines of code below:
...
for (Map.Entry<String, Integer> column : this.columns.entrySet())
{
this.addColumn(column.getKey(), column.getValue());
}
...
public void addColumn(String name, int size)
{
this.columns.put(name, size); //<--- set the column size
this.defaultModel.addColumn(name); //<--- add the new column
}
this.columns is a Map<String, Integer> filled with column name and his size.
My goal is to set the column type for each columns of my table, for instance: I want the third column render as a checkbox because it's a boolean (indeed, not the default String field rendering). Took a look to how to use tables but I still haven't figured out how to deal with it specially "Using Custom Renderers" chapter and when it uses Array to determine the cell render (?) while storing rows inside the table. I use Vector instead of Arrays to manipulate data... I'm a bit confused...
How could I set a custom render on columns?
override getColumnClass() in DefaultTableModel as below:
this.defaultModel = new DefaultTableModel(){
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex==2 ? Boolean.class : Object.class;
}
}

Modify Entire Column(s) in JTable

I have a JTable which is formed by importing data from a text file. It is a huge table with about 522 columns and thousands of rows. Many cells in this table are empty as well.
Now, I want to be able to apply some mathematical operations to the data available in certain columns. So right now, I can select multiple columns, but I dont know how to go about getting these values. I know I'll need an array of arrays where I can store the value from the table columns and then modify each value based on my algorithm.
Right now, to start it off simply, I just want to be able to print out the values in the select column (just one to keep it simple) but I cant do that, I get the value of a particular cell printed 2-4 times. My testing code is as follows:
For Selection entire columns, I am using this code:
public static void selectWholeColumn(final JTable table)
{
final JTableHeader header = table.getTableHeader();
header.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
int col = header.columnAtPoint(e.getPoint());
if(header.getCursor().getType() == Cursor.E_RESIZE_CURSOR)
{
e.consume();
}
else
{
table.setColumnSelectionAllowed(true);
table.setRowSelectionAllowed(false);
table.clearSelection();
table.setColumnSelectionInterval(col,col);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
}
}
});
}
In my GUI I have a button which when clicked fires a backend class and this method which takes in a JTable as a parameter is executed to print out the values of all rows in the selected Column:
public void filterData(final JTable table)
{
TableModel model = table.getModel();
table.setCellSelectionEnabled(true);
table.setColumnSelectionAllowed(true);
table.setRowSelectionAllowed(false);
ListSelectionModel cellSelectionModel = table.getSelectionModel();
cellSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
cellSelectionModel.addListSelectionListener(new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent e)
{
String selectedData = null;
int[] selectedRow = table.getSelectedRows();
int[] selectedColumns = table.getSelectedColumns();
for (int i = 0; i < selectedColumns.length; i++)
{
for (int j = 0; j < selectedRow.length; j++)
{
selectedData = (String) table.getValueAt(selectedRow[i], selectedColumns[j]);
}
}
System.out.println("Selected: " + selectedData);
}
});
Any suggestions as to how I could print or basically get the values of all rows in a selected Column or columns, so that I can modify the data in them at once?
Thank you!
Looping though the TableModel and using getValueAt() in a ListSelectionListener is a reasonable approach, although your listener will be invoked for each change. It's not clear where you're having trouble; you can
Concatenate the selected values using StringBuilder.
Accumulate the selected values in a List<String>.
Maintain the selection in your TableModel, as shown here using Set<Integer>.
Operate on the selection in the ItemHandler of a TableCellRenderer, as shown here.

How can I delete the column and its underlying data without hiding it in jtable

I am having difficulties with deleting the actual data under a particular column which I am trying to delete.
I actually want to delete the column and its underlying data. I am able to insert new columns but when I delete
and insert again, the old columns which I previously deleted pop up again.
Any sort help is appreciated.
Thank you in advancce.
The data is stored in the TableModel.
Deleting the column from the ColumnModel will only prevent the view (the JTable) from showing it.
In order to remove it, you need to tell the TableModel to remove the column data as well.
Depending on you implementation, you could use JTable.setValueAt(value, row, column) or TableModel.setValueAt(value, row, column), which ever is more convenient.
This of course assumes you've implemented the setValueAt method
public void removeColumnAndData(JTable table, int vColIndex) {
MyTableModel model = (MyTableModel)table.getModel();
TableColumn col =table.getColumnModel().getColumn(vColIndex);
int columnModelIndex = col.getModelIndex();
Vector data = model.getDataVector();
Vector colIds = model.getColumnIdentifiers();
// Remove the column from the table
table.removeColumn(col);
// Remove the column header from the table model
colIds.removeElementAt(columnModelIndex);
// Remove the column data
for (int r=0; r<data.size(); r++) {
Vector row = (Vector)data.get(r);
row.removeElementAt(columnModelIndex);
}
model.setDataVector(data, colIds);
// Correct the model indices in the TableColumn objects
// by decrementing those indices that follow the deleted column
Enumeration<TableColumn> enum1 = table.getColumnModel().getColumns();
for (; enum1.hasMoreElements(); ) {
TableColumn c = (TableColumn)enum1.nextElement();
if (c.getModelIndex() >= columnModelIndex) {
c.setModelIndex(c.getModelIndex()-1);
}
}
model.fireTableStructureChanged();
}
/*MyDefaultTableModel class**/
class MyTableModel extends DefaultTableModel
{
String columns[];
int size;
public MyTableModel(String col[],int size)
{
super(col,size);
columns = col;
this.size=size;
}
public Vector getColumnIdentifiers()
{
return columnIdentifiers;
}
}

Categories