TableView, TableColumns vanish off the right edge when resizing - javafx-8

Using some custom resizing behaviour I'm losing columns off the right side of the TableView. I have to use UNCONSTRAINED_RESIZE_POLICY (or maybe write a custom POLICY) so that I can size some of the columns to their content.
I have some custom behaviour for the resizing of columns in the TableViews I use in my application.
I use the reflection pattern to autoresize some columns to their content when the data first populates. The remaining columns width is set to a proportion of the remaining width (if there are 3 columns not being autoresized then remaining width/3=column width).
I also have a column width listener which will listen for when a user drags column widths or double clicks on the header divider to size the column to it's content. I also listen to the width of the table itself and then assign any new extra width to the last column.
The above works ok but the issue is when a user resizes a column or multiple columns to the point the last column is as small as it can go columns will start to getting pushed off the right side of the TableView. It makes sense it would do this as I have my POLICY set to UNCONSTRAINED. I obviously can't use CONSTRAINED_RESIZE_POLICY or the above logic won't work.
Is there a custom policy out there that will reduce the rightmost columns inside 1 by 1 as the user increases the column width, so the right column first until it's as small as it can be, then the next rightmost and so on. Or do I need to write this behaviour? I did come across a Koitlin based POLICY in TorpedoFX that looked interesting but I'd rather stay pure Java.
Basically the outcome I want is what I have now but any user resizing just reduces the right-most column to a minimum size, then the next right-most and so on until all the columns to the right of the column the user is resizing are at minimum size but are still visible on the TableView. If there are no columns to the right that can be resized then the user shouldn't be able to resize their column without first resizing a column to the left.
Columns should never disappear off the right side of the TableView.
I've written a test class that mimics this behaviour, it's slightly verbose in places and would be refactored in the real application.
package application;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewSample extends Application {
private TableView<TableData> table = new TableView<TableData>();
private ObservableList<TableData> data = FXCollections.observableArrayList();
private boolean columnResizeOperationPerformed = false;
private String resizeThreeColumn = "";
private String resizeFourColumn = "";
private String resizeSixColumn = "";
private final ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setWidth(1300);
stage.setHeight(600);
TableColumn<TableData, String> oneColumn = new TableColumn<>("One");
TableColumn<TableData, String> twoColumn = new TableColumn<>("Two");
TableColumn<TableData, String> threeColumn = new TableColumn<>("Three");
TableColumn<TableData, String> fourColumn = new TableColumn<>("Four");
TableColumn<TableData, String> fiveColumn = new TableColumn<>("Five");
TableColumn<TableData, String> sixColumn = new TableColumn<>("Six");
TableColumn<TableData, String> sevenColumn = new TableColumn<>("");
TableColumn<TableData, String> eightColumn = new TableColumn<>("");
TableColumn<TableData, String> nineColumn = new TableColumn<>("Nine");
TableColumn<TableData, String> tenColumn = new TableColumn<>("Ten");
TableColumn<TableData, String> elevenColumn = new TableColumn<>("Eleven");
TableColumn<TableData, String> twelveColumn = new TableColumn<>("Twelve");
TableColumn<TableData, String> thirteenColumn = new TableColumn<>("Thirteen");
TableColumn<TableData, String> lastColumn = new TableColumn<>("Last");
table.setEditable(false);
table.setPrefWidth(1100);
table.setMaxWidth(1100);
table.setItems(data);
table.getColumns().addAll(oneColumn, twoColumn, threeColumn, fourColumn, fiveColumn, sixColumn, sevenColumn, eightColumn, nineColumn, tenColumn, elevenColumn, twelveColumn, thirteenColumn, lastColumn);
table.setFixedCellSize(25.0);
// This cellValueFactory code could be refactored in the real application
oneColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("oneColumn"));
oneColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getOneColumn());
}
});
twoColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("twoColumn"));
twoColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getTwoColumn());
}
});
threeColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("threeColumn"));
threeColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getThreeColumn());
}
});
fourColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("fourColumn"));
fourColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getFourColumn());
}
});
fiveColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("fiveColumn"));
fiveColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getFiveColumn());
}
});
sixColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("sixColumn"));
sixColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getSixColumn());
}
});
sevenColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("sevenColumn"));
sevenColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getSevenColumn());
}
});
eightColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("eightColumn"));
eightColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getEightColumn());
}
});
nineColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("nineColumn"));
nineColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getNineColumn());
}
});
tenColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("tenColumn"));
tenColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getTenColumn());
}
});
elevenColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("elevenColumn"));
elevenColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getElevenColumn());
}
});
twelveColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("twelveColumn"));
twelveColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getTwelveColumn());
}
});
thirteenColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("thirteenColumn"));
thirteenColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getThirteenColumn());
}
});
lastColumn.setCellValueFactory(new PropertyValueFactory<TableData, String>("lastColumn"));
lastColumn.setCellValueFactory(new Callback<CellDataFeatures<TableData, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<TableData, String> p) {
return new ReadOnlyObjectWrapper(p.getValue().getLastColumn());
}
});
// using CONSTRAINED_RESIZE_POLICY will cause all kinds of odd behaviour because of the autoresize and then the columnWidthListener below.
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
table.getItems().addListener(new ListChangeListener<TableData>() {
#Override
public void onChanged(Change<? extends TableData> c) {
// check to see if any of the data coming in has column 3 or 4 values that columns can be resized with
if (!columnResizeOperationPerformed) {
boolean outerBreak = false;
while (c.next() && !outerBreak) {
List<? extends TableData> addedSubList = c.getAddedSubList();
if (!addedSubList.isEmpty()) {
for (TableData data : addedSubList) {
outerBreak = checkForColThreeOrFourData(data);
}
}
}
}
// resize some columns to fit contents, other columns to take up remaining space
if (!columnResizeOperationPerformed && !table.getItems().isEmpty()) {
// only prevent future column resizing if the threeColumn has some valid data to size on
if (resizeThreeColumn.length() > 0) {
columnResizeOperationPerformed = true;
}
double totalWidth = 0;
totalWidth = autosizeColumn(oneColumn);
totalWidth += autosizeColumn(threeColumn);
totalWidth += autosizeColumn(fourColumn);
totalWidth += autosizeColumn(sixColumn);
totalWidth += autosizeColumn(sevenColumn);
totalWidth += autosizeColumn(eightColumn);
totalWidth += autosizeColumn(nineColumn);
totalWidth += autosizeColumn(tenColumn);
totalWidth += autosizeColumn(elevenColumn);
totalWidth += autosizeColumn(twelveColumn);
totalWidth += autosizeColumn(lastColumn);
double remainingWidth = table.getWidth() - totalWidth;
sizeColumn(twoColumn, remainingWidth / 4.0);
sizeColumn(fiveColumn, remainingWidth / 4.0);
sizeColumn(thirteenColumn, remainingWidth / 4.0);
table.requestLayout();
}
}
});
ChangeListener<? super Number> columnWidthListener = (obs, ov, nv) -> {
double totalWidth = table.getColumns().stream()
.filter(tc -> !tc.equals(lastColumn))
.mapToDouble(TableColumnBase::getWidth)
.sum();
sizeColumn(lastColumn, table.getWidth() - totalWidth);
};
// listen for any column resizing or table width changes and assign extra width to the lastColumn above
table.getColumns().stream()
.filter(tc -> !tc.equals(lastColumn)).forEach(tc -> {
tc.widthProperty().addListener(columnWidthListener);
});
table.widthProperty().addListener(columnWidthListener);
HBox hBox = new HBox(table);
HBox.setHgrow(table, Priority.ALWAYS);
((Group) scene.getRoot()).getChildren().addAll(hBox);
stage.setScene(scene);
stage.show();
// create Task to update the table data after the UI is constructed so that the column autoresizing code above is applied as data is populated.
Task task = new Task() {
#Override
protected Object call() {
try {
Thread.sleep(100);
Platform.runLater(() -> {
updateTableData();
});
}
catch (Exception ex) {}
return null;
}
};
executorService.submit(task);
}
/**
* A test version of a check from the real application to make sure resizing of columns happens when column data of specific columns is valid
*
* #param tableData
* #return
*/
private boolean checkForColThreeOrFourData(TableData tableData) {
if (resizeThreeColumn.length() == 0) {
resizeThreeColumn = tableData.getThreeColumn();
}
if (resizeFourColumn.length() == 0) {
resizeFourColumn = tableData.getFourColumn();
}
if (resizeSixColumn.length() == 0) {
resizeSixColumn = tableData.getSixColumn();
}
if ((resizeThreeColumn.length() > 0) && resizeFourColumn.length() > 0 && resizeSixColumn.length() > 0) { return true; }
return false;
}
public void sizeColumn(TableColumn<?, ?> column, double width) {
column.setPrefWidth(width);
}
public static double autosizeColumn(TableColumn<?, ?> column) {
final TableView<?> tableView = column.getTableView();
final Skin<?> skin = tableView.getSkin();
final int rowsToMeasure = -1;
try {
Method method = skin.getClass().getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
method.setAccessible(true);
method.invoke(skin, column, rowsToMeasure);
}
catch (Exception e) {
e.printStackTrace();
}
return column.getWidth();
}
private void updateTableData() {
data.setAll(Arrays.asList(new TableData("Manufacturer1", "User 1", "value12345", "desc12345", "defaultName", "17:04:49 15/05/19", "200", "0", "0", "3", "12", "2", "16-15-14", "80"),
new TableData("Manufacturer2", "User 2", "value67890", "desc67890", "", "17:06:38 15/05/19", "100", "0", "0", "3", "11", "2", "16-15-14", "82")));
}
class TableData implements Serializable {
private static final long serialVersionUID = 1L;
private String oneColumn;
private String twoColumn;
private String threeColumn;
private String fourColumn;
private String fiveColumn;
private String sixColumn;
private String sevenColumn;
private String eightColumn;
private String nineColumn;
private String tenColumn;
private String elevenColumn;
private String twelveColumn;
private String thirteenColumn;
private String lastColumn;
public TableData(String oneColumn, String twoColumn, String threeColumn, String fourColumn, String fiveColumn, String sixColumn, String sevenColumn, String eightColumn, String nineColumn, String tenColumn, String elevenColumn,
String twelveColumn, String thirteenColumn, String lastColumn) {
this.oneColumn = oneColumn;
this.twoColumn = twoColumn;
this.threeColumn = threeColumn;
this.fourColumn = fourColumn;
this.fiveColumn = fiveColumn;
this.sixColumn = sixColumn;
this.sevenColumn = sevenColumn;
this.eightColumn = eightColumn;
this.nineColumn = nineColumn;
this.tenColumn = tenColumn;
this.elevenColumn = elevenColumn;
this.twelveColumn = twelveColumn;
this.thirteenColumn = thirteenColumn;
this.lastColumn = lastColumn;
}
public String getOneColumn() {
return oneColumn;
}
public String getTwoColumn() {
return twoColumn;
}
public String getThreeColumn() {
return threeColumn;
}
public String getFourColumn() {
return fourColumn;
}
public String getFiveColumn() {
return fiveColumn;
}
public String getSixColumn() {
return sixColumn;
}
public String getSevenColumn() {
return sevenColumn;
}
public String getEightColumn() {
return eightColumn;
}
public String getNineColumn() {
return nineColumn;
}
public String getTenColumn() {
return tenColumn;
}
public String getElevenColumn() {
return elevenColumn;
}
public String getTwelveColumn() {
return twelveColumn;
}
public String getThirteenColumn() {
return thirteenColumn;
}
public String getLastColumn() {
return lastColumn;
}
}
}
As mentioned above this code will auto resize the selected columns and assign the remaining width equally to the other columns.
It will listen to user column width adjustments correctly.
What it won't do is prevent the columns to the right edge vanishing off the view. I would like the right columns to be reduced in width to accomodate the user column width increase in the order described above, right-most first continuing in from the right as columns reach their minimum.
Thanks for any help.

Related

CheckBoxTableCell select entire column

I can not get the value of the checkbox, whether it is selected or not.
I can not select or deselect the entire column of CheckBoxTableCell
//CLASS MODELO
public class Cidade{
private Integer codigo;
private String descricao;
public AAA(Integer codigo, String descricao) {
super();
this.codigo = codigo;
this.descricao = descricao;
}
public Integer getCodigo() {
return codigo;
}
public String getDescricao() {
return descricao;
}
}
//CREATION OF COLUMNS OF TABLEVIEW
TableView tbView = new TableView();
tbView.setEditable(true);
TableColumn colCheck = new TableColumn();
colCheck.setCellFactory(CheckBoxTableCell.forTableColumn(colCheck));
CheckBox cbk = new CheckBox();
cbk.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// TODO Auto-generated method stub
if (!tbView.getItems().isEmpty())
if (newValue)
for (int i = 0; i < tbView.getItems().size(); i++) {
((CheckBox) ((TableColumn) tbView.getColumns().get(0)).getGraphic()).selectedProperty().get();
}
}
});
colCheck.setGraphic(cbk);
tbView.getColumns().add(colCheck);
TableColumn colCode = new TableColumn("Codido");
colCode.setCellValueFactory(new PropertyValueFactory("codigo"));
TableColumn colDescricao = new TableColumn("Descricao");
colCode.setCellValueFactory(new PropertyValueFactory("descricao"));
tbView.getColumns().addAll(colCode,colDescricao);
The CheckBoxTableCell expects a selectedStateCallback, which is a function mapping the row index to an ObservableValue<Boolean> that determines if the check box should be checked. If the supplied observable value is also a WritableValue<Boolean>, then when the user checks or unchecks the check box, the value will be updated.
The simplest case for this is when the model for the table has a boolean property which is represented by the check box. However, this doesn't have to be the case. For example, you could create a map from your table items to a boolean property, and use the properties in that map for the selected state of the check boxes:
Map<T, BooleanProperty> checkedRows = new HashMap<>();
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(i ->
checkedRows.computeIfAbsent(table.getItems().get(i), p -> new SimpleBooleanProperty())));
Here you would replace T with the actual type of your table.
You probably want to ensure the map doesn't hold references to items that are no longer part of your table:
// clear obsolete table items from map:
table.getItems().addListener((Change<? extends T> c) -> {
if (c.wasRemoved()) {
c.getRemoved().forEach(checkedRows::remove);
}
});
Now you would just use the map to check for which items are checked:
T item = ... ;
boolean itemIsChecked = checkedRows.getOrDefault(item, new SimpleBooleanProperty(false)).get() ;
or get all the checked items with
List<T> checkedItems = checkedRows.entrySet().stream()
.filter(e -> e.getValue().get())
.map(Entry::getKey)
.collect(Collectors.toList());
You can select everything with
table.getItems().forEach(item ->
checkedRows.computeIfAbsent(item , item -> new SimpleBooleanProperty()).set(true));
and similarly deselect by setting to false.
Here is a complete SSCCE using the usual address book example:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewWithCheckBoxColumn extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.setEditable(true);
TableColumn<Person, Void> checkColumn = new TableColumn<>();
table.getColumns().add(checkColumn);
Map<Person, BooleanProperty> checkedRows = new HashMap<>();
// clear obsolete table items from map:
table.getItems().addListener((Change<? extends Person> c) -> {
if (c.wasRemoved()) {
c.getRemoved().forEach(checkedRows::remove);
}
});
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(i ->
checkedRows.computeIfAbsent(table.getItems().get(i), p -> new SimpleBooleanProperty())));
CheckBox checkAll = new CheckBox();
checkAll.setOnAction(e -> {
if (checkAll.isSelected()) {
table.getItems().forEach(p ->
checkedRows.computeIfAbsent(p, person -> new SimpleBooleanProperty()).set(true));
} else{
checkedRows.values().stream().forEach(checked -> checked.set(false));
}
});
checkColumn.setGraphic(checkAll);
checkColumn.setEditable(true);
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.getColumns().add(column("Email", Person::emailProperty));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
Button verify = new Button("Verify");
verify.setOnAction(evt -> {
checkedRows.entrySet().stream().filter(e -> e.getValue().get()).map(Entry::getKey)
.map(Person::getFirstName).forEach(System.out::println);
});
BorderPane root = new BorderPane(table);
BorderPane.setAlignment(verify, Pos.CENTER);
BorderPane.setMargin(verify, new Insets(5));
root.setBottom(verify);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static void main(String[] args) {
launch(args);
}
}
with the usual Person model class:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}

GWT CellTree inside a DataGrid does not react to mouse-clicks

I have put a GWT-CellTree in a DataGrid. It works except for one thing: When clicking on the tree-expand-icon, nothing happens. I have tried to minimize the
code needed to reproduce the problem...
How can this be fixed ?
best regards, Magnus
package com.raybased.gwt.configtool.client.grid;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
public class HelloWorld implements EntryPoint {
public void onModuleLoad() {
DataGrid grid= (DataGrid) new NodesGrid().getTable();
grid.setWidth("100%");
SimpleLayoutPanel slp = new SimpleLayoutPanel();
slp.add(grid);
RootLayoutPanel.get().add(slp);
}
}
package com.raybased.gwt.configtool.client.grid;
import com.google.gwt.cell.client.ClickableTextCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.dom.client.Style;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.user.cellview.client.*;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.view.client.ListDataProvider;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
public class NodesGrid {
private AbstractCellTable<Node> table = new DataGrid<>();
private Column nameColumn;
private Column<Node, String> showBlocksColumn;
private final Set<Integer> showBlocks = new HashSet<>();
public Column getNameColumn() {
return nameColumn;
}
public AbstractCellTable<Node> getTable() {
return table;
}
public NodesGrid() {
table.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
table.setWidth("100%");
CustomCellTableBuilder c = new CustomCellTableBuilder(this, table);
table.setTableBuilder(c);
Node nodes = new Node();
nodes.setName("hello");
nodes.setId(123);
Node n2 = new Node();
n2.setName("testing");
nodes.getBlocks().add(n2);
ListDataProvider<Node> dataProvider = new ListDataProvider<>();
dataProvider.addDataDisplay(table);
List<Node> list = dataProvider.getList();
list.add(nodes);
addShowBlocks();
nameColumn = getSorter(list, "Name", Comparator.comparing(Node::getName), Node::getName);
}
private Column getSorter(List<Node> list,
String name,
Comparator<Node> cmp,
Function<Node, String> supplier) {
TextColumn<Node> column = new TextColumn<Node>() {
#Override
public String getValue(Node object) {
return supplier.apply(object);
}
};
table.addColumn(column, name);
column.setSortable(true);
ColumnSortEvent.ListHandler<Node> columnSortHandler = new ColumnSortEvent.ListHandler<>(list);
columnSortHandler.setComparator(column, cmp);
table.addColumnSortHandler(columnSortHandler);
table.getColumnSortList().push(column);
return column;
}
public Column<Node, String> getShowBlocksColumn() {
return showBlocksColumn;
}
public Set<Integer> getShowBlocks() {
return showBlocks;
}
void addShowBlocks() {
SafeHtmlRenderer<String> anchorRenderer = new AbstractSafeHtmlRenderer<String>() {
#Override
public SafeHtml render(String object) {
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("(<a href=\"javascript:;\">").appendEscaped(object)
.appendHtmlConstant("</a>)");
return sb.toSafeHtml();
}
};
showBlocksColumn = new Column<Node, String>(new ClickableTextCell(anchorRenderer)) {
#Override
public String getValue(Node node) {
if (showBlocks.contains(node.getId())) {
return "hide blocks";
} else {
return "show blocks";
}
}
};
showBlocksColumn.setFieldUpdater(new FieldUpdater<Node, String>() {
#Override
public void update(int index, Node node, String value) {
if (showBlocks.contains(node.getId())) {
showBlocks.remove(node.getId());
} else {
showBlocks.add(node.getId());
}
table.redrawRow(index);
}
});
table.addColumn(showBlocksColumn);
table.setColumnWidth(0, 10, Style.Unit.EM);
}
}
package com.raybased.gwt.configtool.client.grid;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.TreeNode;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;
import java.util.ArrayList;
import java.util.List;
public class NodesTree {
private CellTree tree;
public NodesTree(Node node) {
TreeViewModel model = new CustomTreeModel(node);
tree = new CellTree(model, null);
tree.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
TreeNode rootNode = tree.getRootTreeNode();
openAll(rootNode);
}
private static class TopLevel {
private final String name;
private List<Node> blocks = new ArrayList<>();
public TopLevel(String name) {
this.name = name;
}
public TopLevel(String name, List<Node> blocks) {
this.name = name;
this.blocks = blocks;
}
public String getName() {
return name;
}
public List<Node> getBlocks() {
return blocks;
}
}
private static class CustomTreeModel implements TreeViewModel {
private final List<TopLevel> topLevels;
private final SingleSelectionModel<String> selectionModel
= new SingleSelectionModel<>();
public CustomTreeModel(Node node) {
topLevels = new ArrayList<>();
{
TopLevel blocksNode = new TopLevel("Blocks", new ArrayList(node.getBlocks()));
topLevels.add(blocksNode);
}
{
TopLevel outgoing = new TopLevel("Outgoing");
topLevels.add(outgoing);
}
{
TopLevel incoming = new TopLevel("Incoming");
topLevels.add(incoming);
}
}
public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) {
// LEVEL 0.
ListDataProvider<TopLevel> dataProvider
= new ListDataProvider<>(
topLevels);
Cell<TopLevel> cell = new AbstractCell<TopLevel>() {
#Override
public void render(Context context, TopLevel value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant(" ");
sb.appendEscaped(value.getName());
}
};
return new DefaultNodeInfo<>(dataProvider, cell);
} else if (value instanceof TopLevel) {
// LEVEL 1.
ListDataProvider<Node> dataProvider = new ListDataProvider<>(((TopLevel) value).getBlocks());
Cell<Node> cell = new AbstractCell<Node>() {
#Override
public void render(Context context, Node value, SafeHtmlBuilder sb) {
if (value != null) {
sb.appendHtmlConstant(" ");
sb.appendEscaped("(" + value.getName() + ")");
}
}
};
return new DefaultNodeInfo<>(dataProvider, cell);
} else if (value instanceof Node) {
// LEVEL 2 - LEAF.
String params = "test";
ArrayList<String> str = new ArrayList<>();
str.add(params);
ListDataProvider<String> dataProvider = new ListDataProvider<>(str);
return new DefaultNodeInfo<>(dataProvider, new TextCell(), selectionModel, null);
}
return null;
}
public boolean isLeaf(Object value) {
if (value instanceof String) {
return true;
}
return false;
}
}
private void openAll(TreeNode rootNode) {
if (rootNode == null) return;
for (int i = 0; i < rootNode.getChildCount(); i++) {
TreeNode node = rootNode.setChildOpen(i, true);
openAll(node);
}
}
public Widget getWidget() {
return tree;
}
}
package com.raybased.gwt.configtool.client.grid;
import com.google.gwt.dom.builder.shared.DivBuilder;
import com.google.gwt.dom.builder.shared.TableCellBuilder;
import com.google.gwt.dom.builder.shared.TableRowBuilder;
import com.google.gwt.dom.client.Style;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.AbstractCellTable;
import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.view.client.SelectionModel;
public class CustomCellTableBuilder extends AbstractCellTableBuilder<Node> {
NodesGrid nodesGrid;
private final String rowStyle;
private final String selectedRowStyle;
private final String cellStyle;
private final String selectedCellStyle;
/**
* Construct a new table builder.
*
* #param cellTable the table this builder will build rows for
*/
public CustomCellTableBuilder(NodesGrid nodesGrid, AbstractCellTable<Node> cellTable) {
super(cellTable);
this.nodesGrid = nodesGrid;
AbstractCellTable.Style style = cellTable.getResources().style();
rowStyle = style.evenRow();
selectedRowStyle = " " + style.selectedRow();
cellStyle = style.cell() + " " + style.evenRowCell();
selectedCellStyle = " " + style.selectedRowCell();
}
#Override
protected void buildRowImpl(Node rowValue, int absRowIndex) {
// Calculate the row styles.
SelectionModel<? super Node> selectionModel = cellTable.getSelectionModel();
boolean isSelected =
(selectionModel == null || rowValue == null) ? false : selectionModel
.isSelected(rowValue);
StringBuilder trClasses = new StringBuilder(rowStyle);
if (isSelected) {
trClasses.append(selectedRowStyle);
}
String cellStyles = cellStyle;
if (isSelected) {
cellStyles += selectedCellStyle;
}
TableRowBuilder row = startRow();
row.className(trClasses.toString());
buildRow(row, rowValue, cellStyles, nodesGrid.getShowBlocksColumn());
buildRow(row, rowValue, cellStyles, nodesGrid.getNameColumn());
row.endTR();
if (nodesGrid.getShowBlocks().contains(rowValue.getId())) {
buildBlockRow(rowValue, cellStyles);
}
}
void buildRow(TableRowBuilder row, Node rowValue, String cellStyles, Column column) {
TableCellBuilder td = row.startTD();
td.className(cellStyles);
td.style().outlineStyle(Style.OutlineStyle.NONE).endStyle();
renderCell(td, createContext(0), column, rowValue);
td.endTD();
}
private void buildBlockRow(Node rowValue, String cellStyles) {
TableRowBuilder row = startRow();
buildCell(rowValue, cellStyles, row);
row.endTR();
}
private void buildCell(Node rowValue, String cellStyles, TableRowBuilder row) {
NodesTree hw = new NodesTree(rowValue);
TableCellBuilder td = row.startTD().colSpan(5);
td.className(cellStyles);
DivBuilder div = td.startDiv();
String t = hw.getWidget().getElement().getInnerHTML();
div.html(SafeHtmlUtils.fromTrustedString(t));
div.end();
td.endTD();
}
}
package com.raybased.gwt.configtool.client.grid;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Node implements Serializable {
String name;
List<Node> blocks = new ArrayList<>();
String params;
int id;
public Node() {
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Node> getBlocks() {
return blocks;
}
}

JavaFX Table searching with optional column

I have a tableview that i populate using an observablelist, ObservableList<Member>. The some attributes in Member object are optional, so the table cell for that row will be empty.
I have implemented FilteredList<Member> and SortedList<Member> although when i search, because of those null values in some cells, a java.lang.NullPointerException is thrown. I have no idea on how to solve this problem.
The following is SSCCE, that demonstrate my problem
package com.yunusfx.javafxcustomcontrols.yunusreproduceproblem;
import javafx.application.Application;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
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.BorderPane;
import javafx.stage.Stage;
public class TableSearch extends Application{
private TableView<Member> tv = new TableView();
private TextField tfSearch = new TextField();
ObservableList<Member> memberList = FXCollections.observableArrayList();
ListProperty<Member> memberListProperty = new SimpleListProperty<>();
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage primaryStage) throws Exception {
TableColumn<Member, String> name = createNameColumn();
TableColumn<Member, Integer> age = createAgeColumn();
TableColumn<Member, String> account = createAccountColumn();
TableColumn<Member, String> location = createLocationColumn();
tfSearch.setPromptText("Search here");
tv.getColumns().addAll(name, age, account, location);
memberListProperty.set(memberList);
tv.itemsProperty().bindBidirectional(memberListProperty);
tv.setItems(memberListProperty);
setData();
FilteredList<Member> filteredData = new FilteredList<>(memberList, p -> true);
tfSearch.textProperty().addListener((observable, oldValue, newValue) -> {
filteredData.setPredicate(Member -> {
if (newValue == null || newValue.isEmpty()) {
return true;
}
String lowerCaseFilter = newValue.toLowerCase();
if (Member.getName().toLowerCase().contains(lowerCaseFilter)) {
return true;
// } else if(Member.getAge().toLowerCase().contains(lowerCaseFilter)){
// No idea how to search if is integer
// return true;
}else if(Member.getLocation().toString().toLowerCase().contains(lowerCaseFilter)){
return true;
}else if(Member.getAccount().toLowerCase().contains(lowerCaseFilter)){
return true;
}
return false;
});
});
SortedList<Member> sortedData = new SortedList<>(filteredData);
sortedData.comparatorProperty().bind(tv.comparatorProperty());
tv.setItems(sortedData);
BorderPane borderPane = new BorderPane();
borderPane.setTop(tfSearch);
borderPane.setCenter(tv);
Scene scene = new Scene(borderPane, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn createNameColumn() {
TableColumn<Member, String> colName = new TableColumn("Name");
colName.setMinWidth(25);
colName.setId("colName");
colName.setCellValueFactory(new PropertyValueFactory("name"));
return colName;
}
private TableColumn createAgeColumn() {
TableColumn<Member, Integer> colAge = new TableColumn("Age");
colAge.setMinWidth(25);
colAge.setId("colAge");
colAge.setCellValueFactory(new PropertyValueFactory("age"));
return colAge;
}
private TableColumn createAccountColumn() {
TableColumn<Member, String> colAccount = new TableColumn("Account");
colAccount.setMinWidth(25);
colAccount.setId("colAccount");
colAccount.setCellValueFactory(new PropertyValueFactory("account"));
return colAccount;
}
private TableColumn createLocationColumn() {
TableColumn<Member, String> colAccount = new TableColumn("Location");
colAccount.setMinWidth(25);
colAccount.setId("colLocation");
colAccount.setCellValueFactory(new PropertyValueFactory("location"));
return colAccount;
}
private void setData(){
Member m = new Member();
m.setAccount("we123");
m.setAge(456);
m.setLocation("Nairobi");
m.setName("Member 1");
memberList.add(m);
Member m1 = new Member();
m1.setAccount("OP5623");
m1.setAge(321);
m1.setLocation("Mombasa");
m1.setName("Doe");
memberList.add(m1);
Member m2 = new Member();
m2.setAge(569);
m2.setLocation("Meru");
m2.setName("John");
memberList.add(m2);
Member m3 = new Member();
m3.setAccount("YGTR665");
m3.setAge(666);
m3.setLocation("Eldoret");
m3.setName("Arif");
memberList.add(m3);
Member m4 = new Member();
m4.setAccount("BHJI58966");
m4.setAge(397);
m4.setName("Yunus");
memberList.add(m4);
}
public class Member {
private int id;
private String name;
private Integer age;
private String account;
private String location;
public Member(){}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
}
Clarification
By optional i mean some attributes in Member object might not have been set hence its table cell will be empty
I wasn't able to add age column to be searchable
Based on James_D's comment i was able to make search work but first check if value is null for optional attributes. For integer, convert it to string then use the string value. The important point is, the code is only dealing with actual data.
Following is what i updated:
FilteredList<Member> filteredData = new FilteredList<>(memberList, p -> true);
tfSearch.textProperty().addListener((observable, oldValue, newValue) -> {
filteredData.setPredicate(Member -> {
if (newValue == null || newValue.isEmpty()) {
return true;
}
String lowerCaseFilter = newValue.toLowerCase();
if (Member.getName().toLowerCase().contains(lowerCaseFilter)) {
return true;
} else if(Integer.toString(Member.getAge()).contains(lowerCaseFilter)){
return true;
}else if(Member.getLocation() != null && Member.getLocation().toLowerCase().contains(lowerCaseFilter)){
return true;
}else if(Member.getAccount() != null && Member.getAccount().toLowerCase().contains(lowerCaseFilter)){
return true;
}
return false;
});
});

I need to show different tool tip for each table cell

Please see this
This is what I want to see... I mean the tool tip
At run time I get this error message for each row that is being loaded.
The tool tip text lies in the an object and is retrieved by thisRow.getCourseTootip(i);
Number of columns in the table varies and I create them and add them to the table view thru code.
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
thisRow = getTableView().getItems().get(getTableRow().getIndex());
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
Error is
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at victoriairene.TheMainFXMLController$1$1.updateItem(TheMainFXMLController.java:434)
at victoriairene.TheMainFXMLController$1$1.updateItem(TheMainFXMLController.java:427)
Line 434 is
thisRow = getTableView().getItems().get(getTableRow().getIndex());
Text for the tool tip for this cell comes from thisRow.getCourseTootip(i).
Can someone tell me, what is wrong with my code? Which object is null ? If it is null, then how do I get to see the correct Tooltip text, in spite of getting error messages for each row ?
I have been struggling with this for one full day.
Please help and thanks in advance.
As requested by Kleopatra I am enclosing the entire Create Table function.
public void createTableForThisSemester(int thisSemester, int numberOfCourses, javafx.collections.ObservableList<AResultRow> TableRows) {
TableView<AResultRow> thisTable = new TableView<>();
thisTable.setContextMenu(contextMenu);
TableColumn<AResultRow, String> tcolRollNo = new TableColumn<>("Roll Number");
tcolRollNo.setEditable(false);
tcolRollNo.setPrefWidth(120);
TableColumn<AResultRow, String> tcolName = new TableColumn<>("Student Name");
tcolName.setEditable(false);
tcolName.setPrefWidth(350);
tcolRollNo.setCellValueFactory(cellData -> cellData.getValue().StudentIDProperty());
tcolName.setCellValueFactory(cellData -> cellData.getValue().StudentNameProperty());
boolean xyz = thisTable.getColumns().addAll(tcolRollNo, tcolName);
// TableColumn[] courseColumn = new TableColumn[numberOfCourses];
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
thisRow = getTableView().getItems().get(getTableRow().getIndex());
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
// System.out.println("# of Rows in Table [" + thisSemester + "] = " + TableRows.size());
TableColumn<AResultRow, String> tcolGPA = new TableColumn<>("GPA");
tcolGPA.setEditable(false);
tcolGPA.setPrefWidth(80);
tcolGPA.setStyle("-fx-alignment: CENTER; font-weight:bold;");
tcolGPA.setCellValueFactory(cellData -> cellData.getValue().returnStringGPA());
boolean retVal = thisTable.getColumns().addAll(tcolGPA);
thisTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
thisTable.setItems(TableRows);
thisTable.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
//Check whether item is selected and set value of selected item to Label
if (thisTable.getSelectionModel().getSelectedItem() == null) {
gRollNumber = null;
gStudentName = null;
} else {
gRollNumber = newValue.getStudentID();
gStudentName = newValue.getStudentName();
}
});
ScrollPane thisScrollPane = new ScrollPane();
thisScrollPane.setFitToWidth(true);
thisScrollPane.setFitToHeight(true);
thisScrollPane.setMinHeight((theDetails.getHeight() - 25));
thisScrollPane.setMaxHeight((theDetails.getHeight() - 25));
thisScrollPane.setMinWidth((theDetails.getWidth() - 25));
thisScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
Tab thisTab = tabs.getTabs().get(thisSemester);
thisTab.setContent(thisScrollPane);
thisScrollPane.setContent(thisTable);
}
I am repeating the hierarchy again - please excuse.
Table view is associated with an observablelist named ATableRows which a class ATableRow.
ATableRow contains several members and one of them is an array of class ACourseResult.
I need to know the ROW number and the array index (which is actually the Table Column number for that Cell) before I can retrieve the text for the tooltip.
Thing is the code works... except for the runtime error of null pointer. I still do not understand what the CellFactory and CellValueFactories do. Sorry about that. Oracle's documents do not say what they do......
While I am at this.... I want to tell you that my TABLE is READ ONLY. Do I Have to use the Observable List ? Can't I do this by setting values directly to each cell (just a curiosity).
Thanks in advance and sorry if my questions seem dumber.
Thanks to JAMES my problem is solved.... I am enclosing the modified code for others.
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
String str = TableRows.get(1).getGrade(courseNo);
final int i = courseNo;
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(i));
thisColumn.setCellFactory(new Callback<TableColumn<AResultRow, String>, TableCell<AResultRow, String>>() {
public TableCell<AResultRow, String> call(TableColumn<AResultRow, String> column) {
return new TableCell<AResultRow, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item);
AResultRow thisRow = new AResultRow();
final int k = this.getIndex(); **// These are the changes suggested by James**
thisRow = getTableView().getItems().get(k); ***// These are the changes suggested by James***
// thisRow = getTableView().getItems().get(getTableRow().getIndex()); <- this is the old code commented out.
final Tooltip tip= new Tooltip();
tip.setText(thisRow.getCourseTootip(i));
setTooltip(tip);
tip.setStyle("-fx-background-color: pink; -fx-text-fill: black; -fx-font: normal normal 12pt \"Times New Roman\"");
}
}
};
}
});
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
The quick, and wrong, answer to the question is to point out that TableCell has a getTableRow() method, that will give you the row data without having to look it up through the underlying data via the row index.
Technically, you should give the TableCell all of the information it needs to work independently when its updateItem() method is called. This would mean restructing the data model such that the table cell is given a structure with both the displayed contents of the cell, and the text to go in the tooltip. It would appear that the AResultRow data structure has two associated lists, one with whatever shows in the cell, and the other with whatever goes into the tooltip. My suggestion would be to refactor that so that it's a single list holding objects which contain both data elements. Once you do that, the rest of the table structure becomes trivial. Here's a working example:
public class SampleTable extends Application {
private BorderPane testPane;
class TestPane extends BorderPane {
public TestPane(List<DataModel> dataItems) {
TableView<DataModel> tableView = new TableView<DataModel>();
setCenter(tableView);
TableColumn<DataModel, ClassInfo> column1 = new TableColumn<DataModel, ClassInfo>("column 1");
TableColumn<DataModel, ClassInfo> column2 = new TableColumn<DataModel, ClassInfo>("column 2");
column1.setCellValueFactory(new PropertyValueFactory<DataModel, ClassInfo>("column1Data"));
column2.setCellValueFactory(new PropertyValueFactory<DataModel, ClassInfo>("column2Data"));
column1.setCellFactory(column -> new CustomTableCell());
column2.setCellFactory(column -> new CustomTableCell());
tableView.getColumns().addAll(column1, column2);
tableView.setItems(FXCollections.observableList(dataItems));
}
}
public static void main(String[] args) {
launch(args);
}
public class CustomTableCell extends TableCell<DataModel, ClassInfo> {
#Override
protected void updateItem(ClassInfo item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
if (item != null) {
setText(item.getName());
setTooltip(new Tooltip(item.getDescription()));
}
}
}
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Task Progress Tester");
List<DataModel> dataItems = new ArrayList<DataModel>();
DataModel row1Data = new DataModel();
row1Data.setColumn1Data(new ClassInfo("row1Col1Name", "This is the description for Row1, Column 1"));
row1Data.setColumn2Data(new ClassInfo("row1Col2Name", "This is the description for Row1, Column 2"));
dataItems.add(row1Data);
DataModel row2Data = new DataModel();
row2Data.setColumn1Data(new ClassInfo("row2Col1Name", "This is the description for Row2, Column 1"));
row2Data.setColumn2Data(new ClassInfo("row2Col2Name", "This is the description for Row2, Column 2"));
dataItems.add(row2Data);
testPane = new TestPane(dataItems);
primaryStage.setScene(new Scene(testPane, 300, 250));
primaryStage.show();
}
public class DataModel {
private ObjectProperty<ClassInfo> column1Data = new SimpleObjectProperty<ClassInfo>();
private ObjectProperty<ClassInfo> column2Data = new SimpleObjectProperty<ClassInfo>();
public void setColumn2Data(ClassInfo newValue) {
column2Data.set(newValue);
}
public void setColumn1Data(ClassInfo newValue) {
column1Data.set(newValue);
}
public ObjectProperty<ClassInfo> column1DataProperty() {
return column1Data;
}
}
public class ClassInfo {
private String name;
private String description;
public ClassInfo(String name, String description) {
this.setName(name);
this.setDescription(description);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
}

Cannot understand ComboBoxTableCell in a TableView

I have read everything available on this site and others; I have cut and pasted every line of code ever written on this subject ( am willing to bet). This is what the result is:
final class Books extends Group {
private TableView table = new TableView();
private ObservableList<Book> data = FXCollections.observableArrayList();
final HBox hb = new HBox();
final TextField Title = new TextField();
final TextField Author = new TextField();
final TextField Publisher = new TextField();
final TextField Copywrite = new TextField();
final TextField ISBN = new TextField();
final Boolean CheckedOut = false;
final Label Whom;
final Button addButton = new Button("Add");
Boolean FirstRead = true;
public static class Book {
private final SimpleStringProperty title;
private final SimpleStringProperty author;
private final SimpleStringProperty publisher;
private final SimpleStringProperty copywrite;
private final SimpleStringProperty isbn;
private final BooleanProperty checkedout;
private final SimpleStringProperty who;
Book(String Titl, String Auth, String Publ,
String Cpywrit, String IsBn, Boolean ChkdOut, String WHO) {
this.title = new SimpleStringProperty(Titl);
this.author = new SimpleStringProperty(Auth);
this.publisher = new SimpleStringProperty(Publ);
this.copywrite = new SimpleStringProperty(Cpywrit);
this.isbn = new SimpleStringProperty(IsBn);
this.checkedout = new SimpleBooleanProperty(ChkdOut);
this.who = new SimpleStringProperty(WHO);
}
public boolean isCheckedOut() {
return checkedout.get();
}
public void setCheckedOut(boolean international) {
this.checkedout.set(international);
}
public BooleanProperty isCheckedOutProperty() {
return checkedout;
}
public String getTitle() {
return title.get();
}
public void setTitle(String Title) {
title.set(Title);
}
public String getAuthor() {
return author.get();
}
public void setAutor(String Author) {
author.set(Author);
}
public String getPublisher() {
return publisher.get();
}
public void setPublisher(String Publisher) {
publisher.set(Publisher);
}
public String getCopywrite() {
return copywrite.get();
}
public void setCopywrite(String Copywrite) {
copywrite.set(Copywrite);
}
public String getIsbn() {
return isbn.get();
}
public void setIsbn(String ISBN) {
isbn.set(ISBN);
}
public Boolean getIo() {
return checkedout.get();
}
public void setIo(Boolean CheckedOut) {
checkedout.set(CheckedOut);
}
public String getWho() {
return who.get();
}
public void setWho(String Who) {
who.set(Who);
}
public ObservableValue<String> whoProperty() {
return who;
}
}
public Books(final File User) throws IOException {
this.Whom = new Label("inLibrary");
this.data = FXCollections.<Book>observableArrayList(
(Book bk) -> new Observable[]{bk.isCheckedOutProperty()
});
final PhoneList p = new PhoneList(User);
final Label label = new Label("Book List");
label.setFont(new Font("Arial", 20));
table.setPrefSize(600, 400);
table.setEditable(true);
TableColumn nameCol = bookName();
TableColumn authorCol = bookAuthor();
TableColumn publisherCol = bookPublisher();
TableColumn copywriteCol = bookCopywrite();
TableColumn isbnCol = bookISBN();
final TableColumn<Book, Boolean> ioCol = new TableColumn<>("In/Out");
ioCol.setMinWidth(50);
ioCol.setEditable(true);
ioCol.setCellValueFactory(new PropertyValueFactory<>("isCheckedOut"));
final Callback<TableColumn<Book, Boolean>, TableCell<Book, Boolean>> iocellFactory = CheckBoxTableCell.forTableColumn(ioCol);
ioCol.setCellFactory((TableColumn<Book, Boolean> column) -> {
TableCell<Book, Boolean> iocell = iocellFactory.call(column);
iocell.setAlignment(Pos.CENTER);
return iocell;
});
ioCol.setCellFactory(iocellFactor
final TableColumn<String, Book> whoCol = new TableColumn<>("Who to");
whoCol.setMinWidth(100);
whoCol.setEditable(true);
whoCol.setCellValueFactory(new PropertyValueFactory<>("who"));
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(new StringConverter<Book>() {
#Override
public String toString(Book string) {
return string.getWho();
}
#Override
public Book fromString(String string) {
return null;
}
}, data));
AddBook(nameCol, authorCol, publisherCol, copywriteCol, isbnCol, ioCol, whoCol, User);
data.addListener((javafx.collections.ListChangeListener.Change<? extends Book> change) -> {
while (change.next()) {
if (change.wasUpdated() && FirstRead != true) {
try {
System.out.println("List changed");
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
getChildren().addAll(vbox);
try {
readFile(User);
} catch (Exception ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
private TableColumn bookISBN() {
TableColumn isbnCol = new TableColumn("ISBN #");
isbnCol.setMinWidth(100);
isbnCol.setCellValueFactory(
new PropertyValueFactory<>("isbn"));
isbnCol.setCellFactory(TextFieldTableCell.forTableColumn());
return isbnCol;
}
private TableColumn bookCopywrite() {
TableColumn copywriteCol = new TableColumn("Copywrite");
copywriteCol.setMinWidth(100);
copywriteCol.setCellValueFactory(
new PropertyValueFactory<>("copywrite"));
copywriteCol.setCellFactory(TextFieldTableCell.forTableColumn());
return copywriteCol;
}
private TableColumn bookPublisher() {
TableColumn publisherCol = new TableColumn("Publisher");
publisherCol.setMinWidth(100);
publisherCol.setCellValueFactory(
new PropertyValueFactory<>("publisher"));
publisherCol.setCellFactory(TextFieldTableCell.forTableColumn());
return publisherCol;
}
private TableColumn bookAuthor() {
TableColumn authorCol = new TableColumn("Author");
authorCol.setMinWidth(100);
authorCol.setCellValueFactory(
new PropertyValueFactory<>("author"));
authorCol.setCellFactory(TextFieldTableCell.forTableColumn());
return authorCol;
}
private TableColumn bookName() {
TableColumn nameCol = new TableColumn("Title");
nameCol.setMaxWidth(100);
nameCol.setCellValueFactory(
new PropertyValueFactory<>("title"));
nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
return nameCol;
}
private void AddBook(TableColumn nameCol, TableColumn authorCol, TableColumn publisherCol,
TableColumn copywriteCol, TableColumn isbnCol, TableColumn ioCol, TableColumn whoCol, final File User) {
table.setItems(data);
table.getColumns().addAll(nameCol, authorCol, publisherCol, copywriteCol, isbnCol, ioCol, whoCol);
addButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
addBook();
try {
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void addBook() {
data.add(new Book(
Title.getText(),
Author.getText(),
Publisher.getText(),
Copywrite.getText(),
ISBN.getText(),
CheckedOut,
Whom.getText()
));
Title.clear();
Author.clear();
Publisher.clear();
Copywrite.clear();
ISBN.clear();
}
});
hb.getChildren().addAll(Title, Author, Publisher,
Copywrite, ISBN, addButton);
hb.setSpacing(10);
Title.setPromptText("Tile of Book");
Title.setMaxWidth(nameCol.getPrefWidth());
Author.setMaxWidth(authorCol.getPrefWidth());
Author.setPromptText("Author");
Publisher.setMaxWidth(publisherCol.getPrefWidth());
Publisher.setPromptText("Publisher");
Copywrite.setMaxWidth(copywriteCol.getPrefWidth());
Copywrite.setPromptText("Year Copywrite");
ISBN.setMaxWidth(isbnCol.getPrefWidth());
ISBN.setPromptText("ISBN #");
}
private void writeFile(File User) throws IOException {
File file = new File(User + "/Books.txt");
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter outFile = new PrintWriter(bw);
if (table.getItems() != null) {
data.stream().map((data1) -> {
if (data1.getTitle().equals("")) {
data1.setTitle("No_Title");
}
return data1;
}).map((data1) -> {
if (data1.getAuthor().equals("")) {
data1.setAutor("No_Author");
}
return data1;
}).map((data1) -> {
if (data1.getPublisher().equals("")) {
data1.setPublisher("No_Publisher");
}
return data1;
}).map((data1) -> {
if (data1.getCopywrite().equals("")) {
data1.setCopywrite("No_Copywrite");
}
return data1;
}).map((data1) -> {
if (data1.getIsbn().equals("")) {
data1.setIsbn("No_ISBN");
}
return data1;
}).map((data1) -> {
if (data1.getWho().equals("")) {
data1.setWho("InLibrary");
}
return data1;
}).map((data1) -> {
outFile.println(data1.getTitle());
return data1;
}).map((data1) -> {
outFile.println(data1.getAuthor());
return data1;
}).map((data1) -> {
outFile.println(data1.getPublisher());
return data1;
}).map((data1) -> {
outFile.println(data1.getCopywrite());
return data1;
}).map((data1) -> {
outFile.println(data1.getIsbn());
return data1;
}).map((data1) -> {
outFile.println(data1.getIo());
return data1;
}).forEach((data1) -> {
outFile.println(data1.getWho());
});
outFile.close();
}
}
private void readFile(File User) throws Exception {
try {
String name, author, publisher, copywrite, isbn, whom;
Boolean InOut;
try (Scanner inFile = new Scanner(new File(User + "/Books.txt"))) {
while (inFile.hasNextLine()) {
name = inFile.next();
author = inFile.next();
publisher = inFile.next();
copywrite = inFile.next();
isbn = inFile.next();
InOut = inFile.nextBoolean();
whom = inFile.next();
data.add(new Book(name, author, publisher, copywrite,
isbn, InOut, whom));
}
}
table.setItems(data);
} //insert catch statements
catch (FileNotFoundException exception) {
System.out.println("File not found");
} catch (ArrayIndexOutOfBoundsException AIOOBexception) {
System.out.println("Array Index is out of bounds");
} catch (IllegalArgumentException IAexception) {
System.out.println("Divide by zero error");
} catch (NoSuchElementException NAexception) {
}
FirstRead = false;
}
}
This gives the following error:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: mediatracker.Books$Book cannot be cast to java.lang.String
at mediatracker.Books$1.toString(Books.java:198)
Line 198 starts:
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(new StringConverter<Book>() {
Can anyone type in the "theory" behind the ComboBoxTableCell, and list the parts necessary to accomoplish this. All I want to do is to change the value of the cell extracted from a PhoneList in another file.
I'm just going to assume that line 198 is
return (String) object ;
in the anonymous StringConverter's toString() method. When you post a question with a stack trace, always indicate which is the line in your code to which the exception is pointing.
First, it's always better to use generic types instead of raw types. So instead of
TableColumn whoCol = ...
you should have
TableColumn<S,T> whoCol = ...
where you replace S with the type of the data in the table and T with the type of the data in the column. Since you haven't given a complete example, I have no way of guessing what S is; from the error message it looks like the type of the data in the column might be Book.
Read the Javadocs for the method you are calling. They clearly state what the converter is:
converter - A StringConverter to convert the given item (of type T) to
a String for displaying to the user.
So, assuming that your TableColumn is displaying Books, and assuming data is of type ObservableList<Book>, you should have something like
ObservableList<Book> data = ... ;
TableColumn<S, Book> whoCol = new TableColumn<>("Who to");
// ...
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(new StringConverter<Book>() {
#Override
public String toString(Book book) {
// assuming your Book class defines a getTitle() method, and that's
// how you want to display it in your ComboBox:
return book.getTitle(); // or get a String from book some other way
}
#Override
public Book fromString(String string) {
// I think this is not actually used, as the combo box is not editable
// So you could probably safely just return null here.
// But in general:
Book book = ... ; // create a book from the string
return book ;
}
}, data));
Again, you replace S by whatever you are using for the type of the TableView; and again I had to make guesses at the type of your TableColumn as you didn't provide a complete example. But this should be enough for you to get the idea.
`final TableColumn<Book, String> whoCol = new TableColumn<>("Who to");
whoCol.setMinWidth(100);
whoCol.setEditable(true);
whoCol.setCellValueFactory(new PropertyValueFactory<>("who"));
whoCol.setCellFactory(ComboBoxTableCell.<Book, String>forTableColumn(p.data.get(myIndex()).toString()));
whoCol.setOnEditCommit((TableColumn.CellEditEvent<Book, String> t) -> {
((Book) t.getTableView().getItems().get(
t.getTablePosition().getRow()))
.setWho(t.getNewValue());
try {
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
});`
This works - #James_D thank you for your aid - you sent me in the right direction
Your problems don't seem to have anything to do with a ComboBoxTableCell.
Your toString() method is wrong; you're trying to cast an Object to a String. That's (probably) why you're seeing a java.lang.ClassCastException. Can you verify which line is line 198?
Also, I notice all your fromString() method does is return its argument. What exactly are you trying to achieve here in this code snippet? Can you give us a little more background information?