I have following problem in javafx tableview, my table view is connected to certain model, and normal CRUD operations work with no issues, I also added a column which is not connected to ANY data model, and it just contains a hyperlink on which user can click, and he is prompted with a pop up.
All of this work, what it does not work is, when I click on hyperlink I want to also pass the row value, and normally it works like
tableview.getSelectionModel().getSelectedItem();
But now it does not work, since i am not clicking directly a cell, but I click a hyperlink, and if I click first some row and then hyperlink, I get that row that I highlighted. Is there any way to select row when clicking hyperlink,so I don't have to first click row then hyperlink in same row.
public class RemoveCell<T> extends TableCell<T, Void> {
private final Hyperlink link;
private final Hyperlink link1;
private final HBox pane = new HBox();
public RemoveCell() {
link = new Hyperlink("Remove");
link1 = new Hyperlink("Edit");
pane.getChildren().addAll(link,link1);
link1.setOnAction(evt -> {
//lagerRet();
if(tableView.getSelectionModel().getSelectedItem()!=null) {
System.out.println("not null");
}
else {
System.out.println("null");
}
// remove row item from tableview
// ap.getChildren().removeAll();
//ap.getChildren().setAll(mcon.loadParent(FxmlView.CHART));
PopOver popsy = new PopOver();
try {
popsy.setContentNode(control.loadUni(FxmlView.POP));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//popsy.setContentNode(panemain);
popsy.headerAlwaysVisibleProperty().set(true);;
popsy.show(link);
});
link.setOnAction(evt -> {
// remove row item from tableview
System.out.println("a quick test");
});
}
#Override
protected void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
setGraphic(empty ? null : pane );
//setGraphic(empty ? null : link1 );
}
}
and lastly this how I populate column
testColumn.setCellFactory(tc -> new RemoveCell<>());
It's possible to access the row item via TableRow containing the TableCell.
T item = getTableRow().getItem();
It's also possible to get the index in TableView.items using TableCell.getIndex which allows for removal without searching for the item in the list first.
int itemIndex = getIndex();
getTableView().getItems().remove(itemIndex);
Related
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 am writing a JavaFX app where a series of messages appear in a TableView. When a new message appears, its row in the table should be highlighted, meaning its background color should be orange or something. Once the user clicks it, the background color should clear, acknowledging the message was read. Should be simple.
I've done enough research to realize that I need to use a rowFactory to set or clear a row's background. But I'm struggling with the mechanics of setRowFactory(). The documentation on Oracle is over my head, and every example I pull up online seems radically different than the last one.
Here's what I have:
public class Message {
private boolean readOnce;
private int date;
private String msg;
public Message(int date, String msg, String msg2){
this.readOnce = false;
this.date = date;
this.msg = msg;
}
public boolean isReadOnce() {
return readOnce;
}
public void setReadOnce(){
readOnce = true;
}
// ...and more standard getters & setters here...
}
The TableView is set up in the main controller:
#FXML TableView<Message> messageTable;
#FXML TableColumn<Message, Integer> Col1;
#FXML TableColumn<Message, String> Col2;
ObservableList<Message> tableItems;
// ...
// Setting up the Table:
PropertyValueFactory<Message, Integer> dateProperty = new PropertyValueFactory<Message, Integer>("date");
PropertyValueFactory<Message, String> msgProperty = new PropertyValueFactory<Message, String>("msg");
Col1.setCellValueFactory( dateProperty );
Col2.setCellValueFactory( msgProperty );
messageTable.setItems( tableItems );
// If we click an item in the table: messageTable.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
System.out.println("Troubleshoot: You clicked: "+newSelection.getMsg());
newSelection.setReadOnce(true);
}
});
And if I want to add a new message to the table, I just add it into the observable list:
public void addMsg(int num, String msg){
tableItems.add(new Message(num, msg));
}
So far, pretty easy. But I'm all thumbs when it comes to the rowFactory:
messageTable.setRowFactory(messageTable -> {
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
if(!tmpMsg.isReadOnce()){
row.getStyleClass().add("highlight-message"); // defined in CSS
} else {
row.getStyleClass().add("clear-message"); // defined in CSS
}
return row;
});
To be very honest, I have no idea what I'm doing here. I understand that the rowFactory takes in the entire table and regenerates each row one-by-one. What I don't understand is how does the RowFactory code examine each Message in the table and how can I access them? Originally I thought these line might allow me to see the Message within the row:
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
But when I debug the code, tmpMsg == NULL. So that's a big fat dead end.
Anyone see what I'm doing wrong? I've been researching this for about a week, getting absolutely no-where. Any help anyone can offer is wildly appreciated.
Many thanks,
-RAO
TableRows are created by TableView to fill it's viewport and contain TableCells. At the time they are created the item property still contains the default value null. You could register a listener to that property but usually I prefer overriding the updateItem method of a cell.
Also using PseudoClass is simpler than using style classes. New items can be assigned to a row; this could result in the same style class being added multiple times and even both style classes could be added to the same cell. PseudoClasses however can be switched on/of without the need to take care of removing other classes.
final PseudoClass highlightMessage = PseudoClass.getPseudoClass("highlight-message");
messageTable.setRowFactory(messageTable -> new TableRow<Message>() {
{
selectedProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
Message item = getItem();
if (item != null) {
item.setReadOnce();
pseudoClassStateChanged(highlightMessage, false);
}
}
});
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
pseudoClassStateChanged(highlightMessage, item != null && !item.isReadOnce());
}
});
In a CSS stylesheet you could use rules like this:
.table-row-cell:filled {
/* style for non-highlighted rows */
}
.table-row-cell:filled:highlight-message {
/* style for highlighted rows */
}
Note that this does not allow you to programmatically alter the read state. It updates the state on selecting a cell. You could add a BooleanProperty to Message or use a ObservableSet to store the highlighted messages and update the state of cells from a listener if you need to programmatically update the readOnce property. In the latter case you do not need to store a readOnce property in the Message itself...
I need to click on the button that is already in a column to automatically select the row to be able to get and set the selected item. Otherwise, generate nullpointer. I was thinking of adding a listener that when I click the button select the row directly, but I do not know.
ScreenShot :
This is the code:
botonVisitar.setOnAction((ActionEvent event) -> {
TextInputDialog dialog1 = new TextInputDialog();
Stage stage1 = (Stage) dialog1.getDialogPane().getScene().getWindow();
stage1.getIcons().add(new Image(this.getClass().getResource("icono.jpg").toString()));
dialog1.setTitle("Visita:");
dialog1.setContentText("Ingresar Tipo de visita: (por ejemplo: llamada, mail, mensaje, etc)");
Optional<String> result1 = dialog1.showAndWait();
if (result1.isPresent()) {
TablaVisita.getSelectionModel().getSelectedItem().setTipoVisita(result1.get());
TablaVisita.getSelectionModel().getSelectedItem().setFechaVisita(LocalDate.now());
//If it is not selected i get nullPointer.
}
I assume you're doing this in a custom TableCell used with a cellFactory, which means you can use the TableRow to get the table item:
new TableCell<MyItem, Void>() {
private final Button botonVisitar = new Button("Visitar");
{
botonVisitar.setOnAction((ActionEvent event) -> {
...
if (result1.isPresent()) {
MyItem item = (MyItem) getTableRow().getItem();
item.setTipoVisita(result1.get());
item.setFechaVisita(LocalDate.now());
}
});
}
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
setGraphic(empty ? null : botonVisitar);
}
}
I have a TableView named as "customers" with TableColumns related to customer details.
In the same TableView, I want to add one more TableColum as "Action" in which User should have possibilities to Add,View and delete Product details for particular Customer.
Tried multiple things by googling it but till now didn't get any solution for it.
Just add a TableColumn with a custom cellFactory. BTW: You probably want to use a single column/row for the Buttons, in which case you can simply use HBox or VBox, but you could of course replace the layout in the following code:
TableColumn<Product, Void> buttonColumn = new TableColumn<>("Action");
buttonColumn.setCellFactory(col -> new TableCell<Product, Void>() {
private final VBox container;
{
Button add = new Button("Add");
Button view = new Button("View");
Button delete = new Button("Delete");
delete.setOnAction(evt -> {
// delete this row item from TableView items
getTableView().getItems().remove(getIndex());
});
view.setOnAction(evt -> {
// call some method with the row item as parameter
viewProduct(getTableRow().getItem());
});
add.setOnAction(...);
container = new VBox(5, add, view, delete);
}
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
setGraphic(empty ? null : container);
}
});
tableView.getColumns().add(buttonColumn);
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());