javafx: Custom TableColumn - javafx

I want to make a custom TableColumn that when in editable state it's cells will be auto complete TextFields, here is what I tried:
public static <T,S> void setAutoCompleteTableColumn(TableColumn<T,S> column, List items){
column.setCellFactory(param -> {
return new TableCell<T, S>(){
final TextField textField = new TextField();
#Override
protected void updateItem(S item, boolean empty) {
super.updateItem(item, empty);
if(item == null){
setGraphic(null);
}else {
setGraphic(textField);
AutoCompletionBinding<T> binding = TextFields.bindAutoCompletion(textField,items);
binding.setOnAutoCompleted(event ->{
//handleCompleted.accept(event.getCompletion());
});
}
}
};
});
}
This code have produced (obviously) this:
But I want this form only when the TableView is in edit mode, So I did this instead:
public static <T,S> void setAutoCompleteTableColumn(TableColumn<T,S> column, List items){
column.setCellFactory(param -> {
return new TableCell<T, S>(){
final TextField textField = new TextField();
#Override
protected void updateItem(S item, boolean empty) {
super.updateItem(item, empty);
if(item == null){
setGraphic(null);
}else {
editableProperty().addListener((obs, oldValue, newValue)->{
if(newValue){
setGraphic(textField);
}else{
setText(item.toString());
}
});
AutoCompletionBinding<T> binding = TextFields.bindAutoCompletion(textField,items);
binding.setOnAutoCompleted(event ->{
//handleCompleted.accept(event.getCompletion());
});
}
}
};
});
}
As you can see I added a listener to editableProperty, which resulted in nothing.
What I want is an editable TableColumn like in here, but with auto complete TextField. Any idea how to accomplish this?

Related

JavaFX: add contextmenu to TableView cell

I'm trying to add a context menu for every individual cell in a column, that gets activated by rightclicking on the cell. The menuitems would also depend on the cell clicked.
I would think using a cell factory would be the best way, but I cant figure out how to do it.
Edit: This is my implementation for one of my columns. setText in updateItem() adds the text to my column, but I want to add it as a label, how should I do that?
moColumn = new TableColumn<>("Maandag");
moColumn.setSortable(false);
moColumn.setCellValueFactory(new PropertyValueFactory<>("Mo"));
getColumns().add(moColumn);
moColumn.setCellFactory(new Callback<TableColumn<RowData, Label>, TableCell<RowData, Label>>() {
#Override
public TableCell<RowData, Label> call(TableColumn<RowData, Label> col) {
final TableCell<RowData, Label> cell = new TableCell<RowData, Label>() {
#Override
protected void updateItem(Label item, boolean empty) {
super.updateItem(item, empty);
if(empty){
setText(null);
} else {
setText(item.getText());
}
}
};
//cell.textProperty().bind(cell.getItem().textProperty());
cell.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton() == MouseButton.SECONDARY){
ContextMenu contextMenu = new ContextMenu();
MenuItem menuItem = new MenuItem(cell.getText());
contextMenu.getItems().add(menuItem);
cell.setContextMenu(contextMenu);
}
}
});
return cell;
}
});
Edit: How can I right click on a cell in tableview in Javafx (fxml)?
I've found this, but the link to the blog isn't working anymore...
Changed:
#Override
protected void updateItem(Label item, boolean empty) {
super.updateItem(item, empty);
if(empty){
setText(null);
} else {
setText(item.getText());
}
}
To:
#Override
protected void updateItem(Label item, boolean empty) {
super.updateItem(item, empty);
if(empty){
setText(null);
} else {
setGraphic(item);
}
}

How to enable/disable a button based on boolean property within a tablerow

As the title states, I'm trying to enable/disable a button within a table row based upon a boolean within that table row's data. Here's my code so far:
col.setCellFactory(new Callback<TableColumn<ExampleRow, String>, TableCell<ExampleRow, String>>() {
#Override
public TableCell call(final TableColumn<ExampleRow, String> param){
final Button btn = new Button("Save");
final TableCell<ExampleRow, String> cell = new TableCell<ExampleRow, String>(){
#Override
public void updateItem(String item, boolean empty){
super.updateItem(item, empty);
if(empty){
setGraphic(null);
setText(null);
} else {
btn.setPrefWidth(col.getWidth());
btn.setPadding(Insets.EMPTY);
btn.setOnAction(event -> {
});
setGraphic(btn);
setText(null);
}
}
};
ExampleRow row = (ExampleRow)cell.getTableRow().getItem(); //NPE here
btn.setDisable(!row.hasChanged());
return cell;
}
});
Unfortunately my code breaks on the fifth from the bottom line. If I exclude that line and change the line below to btn.setDisable(true) it works wonderfully. What can I do to disable this button based upon the data in which the button resides?
You aren't using the item anyways, so you could just make it a Boolean and use the value of the changed property. This allows you to enable/disable the button in the updateItem method:
Example:
public static class Item {
private final BooleanProperty changed = new SimpleBooleanProperty();
public final boolean isChanged() {
return this.changed.get();
}
public final void setChanged(boolean value) {
this.changed.set(value);
}
public final BooleanProperty changedProperty() {
return this.changed;
}
}
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView();
table.getItems().addAll(new Item(), new Item(), new Item());
TableColumn<Item, Boolean> column = new TableColumn<>();
column.setCellValueFactory(cd -> cd.getValue().changedProperty());
column.setCellFactory(col -> new TableCell<Item, Boolean>() {
final Button btn = new Button("Save");
{
btn.setOnAction(evt -> {
Item item = (Item) getTableRow().getItem();
item.setChanged(false);
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
btn.setDisable(!item);
setGraphic(btn);
}
}
});
table.getColumns().add(column);
Button btn = new Button("change");
btn.setOnAction((ActionEvent event) -> {
Item item = table.getSelectionModel().getSelectedItem();
if (item != null) {
item.setChanged(true);
}
});
VBox root = new VBox(btn, table);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
BTW: TableView uses the cellFactory to create the cells. The item, table and tableRow properties are updated later. Therefore retrieving any of those values in the cellFactory's call method itself makes no sense, since none of those values have been assigned at that time.

Javafx: Table Column commit on Button click

I have method that make a TableColumn editable that I made according to this tutorial, with the two changes recommended in this question witch are changing focusedProperty with setOnAction and add binding. The method is like this:
public static <T> void setAutoCompleteTableColumn(TableColumn<T,String> column, BiConsumer<T, String> field, String type, BiConsumer handleUpdates, List autoComplete){
column.setCellFactory(param -> {
return new TableCell<T, String>(){
private TextField textField;
#Override
public void startEdit() {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private void createTextField(){
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
textField.setOnAction(event ->{
if(textField.getText() != ""){
commitEdit(textField.getText());
}
});
AutoCompletionBinding<T> binding = TextFields.bindAutoCompletion(textField,autoComplete);
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
};
});
column.setOnEditCommit(event -> {
T model = event.getRowValue();
if (!event.getNewValue().isEmpty()) {
Cloner cloner = new Cloner();
final T oldModel = cloner.deepClone(model);
//Update the old value with the new value
field.accept(model, event.getNewValue());
//Retrieve changes
handleUpdates.accept(oldModel, model);
}
});
}
I use this method in the controller like this:
TableColumn<Purchase, String> nameColumn = new TableColumn<>();
setAutoCompleteTableColumn(nameColumn, (p,s) -> validatePurchaseEdit(p,s), "text", (o, n) -> updateTracker(o, n), appState.items);
I have a button click event that switch the TableView editable to false.
For some reason when this event is executed the current cell that I am editing does commit edit but an other cell(a random one) switches to edit mode. Can some one explain what is happening, and how can I make all cells commit edit?

Graphic on TreeItem dissapears on drag and drop

I have this basic code which adds drag and drop functionality to tree views.
The code works just fine, but when one treeitem is dragged, dropped and added over to another treeitem, the graphic on the treeitem in the original treecell dissapears, while the graphic follows the dragged element and is visible in the new treeitem.
See the image for example, the treeitem "Name" has been dragged over to "Column Name" and lost it's graphic. Alltough the new TreeItem under "Column Name" still has it graphics.
The code for adding drag and drop is this:
private void addDragAndDrop(TreeCell<String> treeCell, MainFXMLController mainFXMLController) {
treeCell.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("setOnDragDetected");
Dragboard db = treeCell.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString(event.toString());
db.setContent(content);
DRAGGEDSOURCE = treeCell;
DRAGGEDINDEX = (treeCell.getTreeView().getSelectionModel().getSelectedIndex());
}
});
treeCell.setOnDragOver(new EventHandler<DragEvent>() {
//brukeren har dragget det over et element
public void handle(DragEvent event) {
DRAGGEDTARGET = treeCell.getTreeItem();
if (event.getGestureSource() != treeCell
&& event.getDragboard().hasString()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
}
});
treeCell.setOnDragDropped(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
DRAGGEDTARGET.getChildren().add(DRAGGEDSOURCE.getTreeItem());
}
);
}
public void makeTreeViewDragAble(TreeView treeView, MainFXMLController mainFXMLController) {
treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> stringTreeView) {
TreeCell<String> treeCell = new TreeCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setText(item);
setGraphic(getTreeItem().getGraphic());
} else {
setText(null);
setGraphic(null);
}
}
};
addDragAndDrop(treeCell, mainFXMLController);
treeView.setEditable(true);
return treeCell;
}
});
}
As #James_D and I have concluded on, this is because I am trying to have two instances of the imageView. The solution is to set a new image view on the old item.

Only show contextMenu when user is right clicking an item

I have the following cellValueFactory on treeView to add contextMenu and dragAndDrop functionality:
treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> stringTreeView) {
TreeCell<String> treeCell = new TreeCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setText(item);
setGraphic(getTreeItem().getGraphic());
final ContextMenu contextMenu = new ContextMenu();
MenuItem item2 = new MenuItem("Delete");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Here I will add some delete functionality");
}
});
contextMenu.getItems().addAll(item2);
treeView.setContextMenu(contextMenu);
setContextMenu(contextMenu);
} else {
setText(null);
setGraphic(null);
}
}
};
addDragAndDrop(treeCell);
treeView.setEditable(true);
return treeCell;
}
});
The drag and drop works great, and the contextMenu shows up, but it shows up on everything I right click on inside the treeview. How can I make it only appear when the user is actually right clicking an item in the treeview?
You're setting the context menu on both the TreeView and the TreeCell. Just set it on the TreeCell. Also, you should remove it in the case that the cell is empty:
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setText(item);
setGraphic(getTreeItem().getGraphic());
final ContextMenu contextMenu = new ContextMenu();
MenuItem item2 = new MenuItem("Delete");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Here I will add some delete functionality");
}
});
contextMenu.getItems().addAll(item2);
// remove this line:
//treeView.setContextMenu(contextMenu);
setContextMenu(contextMenu);
} else {
setText(null);
setGraphic(null);
setContextMenu(null);
}
}

Resources