I have a TableView, where I need to enable the selection of any cells(one at a time). For now, I use this code:
tableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object oldValue, Object newValue) {
if(tableView.getSelectionModel().getSelectedItem() != null)
{
TableView.TableViewSelectionModel selectionModel = tableView.getSelectionModel();
ObservableList selectedCells = selectionModel.getSelectedCells();
TablePosition tablePosition = (TablePosition) selectedCells.get(0);
String val = (String)tablePosition.getTableColumn().getCellData(newValue);
System.out.println("Selected Value: " + val);
selectionTextField.appendText(val);
}
}
});
The problem is, I can't get the values of different cells in the same row after each other. I think it's because it's the same row, the selection listener is not triggered. I tried clearSelection(), but then I get out of bounds exception, and I read somewhere that I cant change the table model in the same listener. Any tips?
Thank you.
If you are using cell selection instead of row selection (in other words, if you have called tableView.getSelectionMode().setCellSelectionEnabled(true);), then you should observe the list of selected cells instead of the selectedItem property. The selectedItem property only indicates the row that is selected, so it only changes if you select a new row.
ObservableList<TablePosition> selectedCells = table.getSelectionModel().getSelectedCells() ;
selectedCells.addListener((ListChangeListener.Change<? extends TablePosition> change) -> {
if (selectedCells.size() > 0) {
TablePosition selectedCell = selectedCells.get(0);
TableColumn column = selectedCell.getTableColumn();
int rowIndex = selectedCell.getRow();
Object data = column.getCellObservableValue(rowIndex).getValue();
}
});
Related
I have JavaFX tableview that I have created dynamically. When one double-clicks on a cell on the tableview, I need to get the name of the column the cell is in and the value of the first cell in the row this cell is in. I have tried searching over google and found no particular solution to this problem. Kindly show me some sample code.
Ok, so first, let's assume your TableView is attached to a model:
public TableView<MyModel> myTable;
where MyModel is something like:
public class MyModel {
private Integer id;
private String name;
// ... etc.
}
so, MyModel is a common POJO. You can have columns of your TableView like:
TableColumn<MyModel, Integer> id = new TableColumn<>("ID");
id.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<MyModel, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("name"));
and then, to add the columns to your table:
myTable.getColumns().addAll(id, name);
Now, let's listen to the click event using a rowFactory:
myTable.setRowFactory(tv -> {
TableRow<MyModel> row = new TableRow<>();
row.setOnMouseClicked(event -> {
// check for non-empty rows, double-click with the primary button of the mouse
if (!row.isEmpty() && event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
MyModel element = row.getItem();
// now you can do whatever you want with the myModel variable.
System.out.println(element);
}
});
return row ;
});
That should do the work.
I have customized a Hyperlink cell here. I want the tableview to select the content when I click this link, but after I add Hyperlink, the tableview's selected seems to be invalid.
tb_uGoodUrl.setCellFactory(new Callback<TableColumn<GoodModel, String>, TableCell<GoodModel, String>>() {
#Override
public TableCell<GoodModel, String> call(TableColumn<GoodModel, String> param) {
TableCell<GoodModel, String> cell = new TableCell<GoodModel, String>() {
private final Hyperlink hyperlink = new Hyperlink();
{
hyperlink.setOnMouseClicked(event -> {
if(event.getClickCount() == 2){
String url = getItem();
hostServices.showDocument(url);
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
}else {
hyperlink.setText(getItem());
setGraphic(hyperlink);
}
}
};
return cell;
}
});
Click on the link, the cell is not selected
If the cell is not selected, a null exception will be reported when the following code is used.
TablePosition pos = tableView.getSelectionModel().getSelectedCells().get(0);
int row = pos.getRow();
// Item here is the table view type:
GoodModel item = tableView.getItems().get(row);
TableColumn col = pos.getTableColumn();
// this gives the value in the selected cell:
String data = (String) col.getCellObservableValue(item).getValue();
The effect you want to achieve is as follows
Rendering
You can manually select the cell, using the table's selection model, when the Hyperlink is clicked on.
// Assuming this code is inside a TableCell implementation
hyperlink.setOnAction(event -> {
event.consume();
getTableView().getSelectionModel().select(getIndex(), getTableColumn());
// show your document
});
I used the onAction property which will be fired when the Hyperlink has been clicked once. This is typical behavior for a hyperlink, but if you want to only perform the action on a double-click then you can keep using your onMouseClicked handler.
Note the above does not take into account multiple-selection mode.
I have a TreeTableView<MyCustomRow> and I want to add columns dynamically. In MyCustomRow i have a Map<Integer, SimpleBooleanProperty> with the values in the row. I'm adding the new column this way:
private TreeTableColumn<MyCustomRow, Boolean> newColumn() {
TreeTableColumn<MyCustomRow, Boolean> column = new TreeTableColumn<>();
column.setId(String.valueOf(colNr));
column.setPrefWidth(150);
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNr));
column.setCellFactory(factory -> new CheckBoxTreeTableCell());
column.setEditable(true);
colNr++;
return column;
}
Then table.getColumns().add(newColumn()).
The problem is when I check a CheckBox in a row, all CheckBoxes in that row become checked. Here is the code for my row:
public class MyCustomRow {
private Map<Integer, SimpleBooleanProperty> values = new HashMap<>();
public MyCustomRow(Map<Integer, Boolean> values) {
values.entrySet().forEach(entry -> this.values
.put(entry.getKey(), new SimpleBooleanProperty(entry.getValue())));
}
public SimpleBooleanProperty getValue(Integer colNr) {
if (!values.containsKey(colNr)) {
values.put(colNr, new SimpleBooleanProperty(false));
}
return values.get(colNr);
}
}
So I set the value of the cell depending on the colNr, i also tried to debug and it seems the values are different in the values map, so I have no idea why all the checkBoxes are checked when I check just one.
In this line,
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNr));
The handler is called when the cells are shown. Hence all of colNr are the newest value, the boolean property of the last index is associated with all cells.
To call the handler with the value at the time of newColumn() is called, for example:
final Integer colNrFixed = colNr;
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNrFixed));
// ...
colNr++;
I've created a simple TableView that is fed with data from a database, and what I want is just to be able to easily change the value of a numeric column of that table with JavaFx.
But... since I have some mental issue or something, I can't make it work.
Below it's the "SpinnerCell" component, and the issue I've been having is that even after the commitEdit is fired, when I get the items from the TableView, no values were altered. What am I missing from this update lifecycle?
import javafx.scene.control.Spinner;
import javafx.scene.control.TableCell;
public class SpinnerTableCell<S, T extends Number> extends TableCell<S, T> {
private final Spinner<T> spinner;
public SpinnerTableCell() {
this(1);
}
public SpinnerTableCell(int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) -> commitEdit(newValue));
}
#Override
protected void updateItem(T c, boolean empty) {
super.updateItem(c, empty);
if (empty || c == null) {
setText(null);
setGraphic(null);
return;
}
this.spinner.getValueFactory().setValue(c);
setGraphic(spinner);
}
}
Because your table cell is always showing the editing control (the Spinner), you bypass the usual table cell mechanism for beginning an edit. For example, in the TextFieldTableCell, if the cell is not in an editing state, then a label is shown. When the user double-clicks the cell, it enters an editing state: the cell's editingProperty() is set to true, and the enclosing TableView's editingCellProperty() is set to the position of the current cell, etc.
In your case, since this never happens, isEditing() is always false for the cell, and as a consequence, commitEdit() becomes a no-op.
Note that the CheckBoxTableCell is implemented similarly: its documentation highlights this fact. (The check box table cell implements its own direct update of properties via the selectedStateCallback.)
So there are two options here: one would be to enter an editing state when the spinner gains focus. You can do this by adding the following to the cell's constructor:
this.spinner.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
getTableView().edit(getIndex(), getTableColumn());
}
});
Another option would be to provide a callback for "direct updates". So you could do something like:
public SpinnerTableCell(BiConsumer<S,T> update, int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) ->
update.accept(getTableView().getItems().get(getIndex()), newValue));
}
and then given a model class for the table, say
public class Item {
private int value ;
public int getValue() { return value ;}
public void setValue(int value) { this.value = value ;}
// ...
}
You could do
TableView<Item> table = ... ;
TableColumn<Item, Integer> valueCol = new TableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getValue()).asObject());
valueCol.setCellFactory(tc -> new SpinnerTableCell<>(Item::setValue, 1));
here is my code
// this event is attached to TableCell
public EventHandler dblclickDescription = new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.PRIMARY)){
if(event.getClickCount() == 2){
printRow(event.getTarget());
}
}
event.consume();
}
};
// print row
public void printRow(Object o){
Text text = (Text) o;
// ??? don't know what to write here
System.out.println(row.toString());
}
1) how do I get from the cell I clicked to the row?
2) can I attach the event to the whole row instead of each column?
EDIT:
3) I thought that I attached the event on TableCell
TableCell cell = TableColumn.DEFAULT_CELL_FACTORY.call(p);
cell.setOnMouseClicked(dblclickDescription);
but when I tested,
event.getSource();// is a TableColumn
event.getTarget();// is a Text if clicked on text
event.getTarget();// is a TableColumn if clicked on empty space, even if that cell has text
is there a way to get TableCell from MouseEvent?
To answer your specific questions:
how do I get from the cell I clicked to the row?
TableCell defines a getTableRow() method, returning the TableRow. So you can do
Object item = cell.getTableRow().getItem();
which will give you the row item from the table (i.e. the correct element of table.getItems()). You can also get this from table.getItems().get(cell.getIndex()) if you prefer.
can I attach the event to the whole row instead of each column?
Yes. Define a rowFactory:
TableView<MyDataType> table = new TableView<>();
table.setRowFactory(tv -> {
TableRow<MyDataType> row = new TableRow<>();
row.setOnMouseClicked(event -> {
if (! row.isEmpty() && event.getButton()==MouseButton.PRIMARY
&& event.getClickCount() == 2) {
MyDataType clickedRow = row.getItem();
printRow(clickedRow);
}
});
return row ;
});
// ...
private void printRow(MyDataType item) {
// ...
}
To get back the index of the row or the selected item, use this code :
Object object = table.getSelectionModel().selectedItemProperty().get();
int index = table.getSelectionModel().selectedIndexProperty().get();
Then you can add a listener when the index / object change like this :
table.getSelectionModel().selectedIndexProperty().addListener((num) -> function());