I'm trying to implement Drag-and-Drop in TreeView.
I create a cell factory and capture the relevant events. It's all right, but drag drag does not end the operation.
This is a sample of the test code:
#FXML
protected void initialize() {
super.initialize();
treeView.setCellFactory(this::createTreeCell);
}
private TreeCell<Group> createTreeCell(TreeView<Group> tv) {
TreeCell<Group> cell = new TreeCell<>() {
#Override
protected void updateItem(Group group, boolean empty) {
super.updateItem(group, empty);
if(empty || group == null) {
setText("");
}
else {
setText(group.getName());
}
}
};
cell.setOnDragDetected(mouseEvent -> {
System.out.println("Drag detected ... ");
ClipboardContent content = new ClipboardContent();
content.putString("Test");
Dragboard dragboard = cell.startDragAndDrop(TransferMode.COPY);
dragboard.setContent(content);
mouseEvent.consume();
});
cell.setOnDragOver(dragEvent -> {
if(dragEvent.getDragboard().hasString()) {
System.out.println("Drag Over ...");
dragEvent.acceptTransferModes(TransferMode.ANY);
}
dragEvent.consume();
});
cell.setOnDragDropped(dragEvent -> {
System.out.println("Drag Dropped ...");
dragEvent.setDropCompleted(true);
dragEvent.consume();
System.out.println("Is Drop completed: " + dragEvent.isDropCompleted());
});
cell.setOnDragDone(dragEvent -> {
System.out.println("Drag Done ...");
dragEvent.consume();
});
return cell;
}
The outcome of the trace I receive is:
Drag detected ...
Drag Over ...
Drag Over ...
.
.
.
Drag Over ...
Drag Over ...
Drag Dropped ...
Is Drop completed: true
Drag Over ...
Drag Over ...
Drag Over ...
Drag Dropped ...
Is Drop completed: true
Drag Over ...
Drag Over ...
Drag Over ...
Related
I have a TableView in my JavaFX application.
I would like to style differently row when it is double-clicked on it, and differently when it is single-clicked.
Here is what I achieve:
final PseudoClass doubleClickPseudoClass = PseudoClass.getPseudoClass("new");
setRowFactory(tableView -> {
final TableRow<Bean> row = new TableRow<Bean>();
row.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && (! row.isEmpty())) {
row.pseudoClassStateChanged(doubleClickPseudoClass, true);
});
return row;
});
However, when the user doubles click on every new row, I want all previously double-clicked rows to be styled without applying "new" class:
row.pseudoClassStateChanged(doubleClickPseudoClass, false);
How can I do that?
Now I have cumulative styled all rows as they are double-clicked.
You shouldn't use TableRows to store the state themselves since new items may be assigned to a TableRow instance. Instead use a property to store the item double-clicked item and use a listener for styling the rows:
final ObjectProperty<Bean> doubleClickedObject = new SimpleObjectProperty<>();
setRowFactory(tableView -> new TableRow<Bean>() {
private void updateStyle() {
pseudoClassStateChanged(doubleClickPseudoClass, !isEmpty() && doubleClickedObject.get() == getItem());
}
private final InvalidationListener listener;
{
listener = o -> updateStyle();
doubleClickedObject.addListener(new WeakInvalidationListener(listener));
setOnMouseClicked(event -> {
if (!isEmpty() && event.getClickCount() == 2) {
doubleClickedObject.set(getItem());
}
});
}
#Override
protected void updateItem(Bean item, boolean empty) {
super.updateItem(item, empty);
updateStyle();
}
});
I've got a table view which you can drag rows to re-position the data. The issue is getting the table view to auto scroll up or down when dragging the row above or below the records within the view port.
Any ideas how this can be achieved within JavaFX?
categoryProductsTable.setRowFactory(tv -> {
TableRow<EasyCatalogueRow> row = new TableRow<EasyCatalogueRow>();
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, new ArrayList<Integer>(categoryProductsTable.getSelectionModel().getSelectedIndices()));
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int dropIndex;
if (row.isEmpty()) {
dropIndex = categoryProductsTable.getItems().size();
} else {
dropIndex = row.getIndex();
}
ArrayList<Integer> indexes = (ArrayList<Integer>) db.getContent(SERIALIZED_MIME_TYPE);
for (int index : indexes) {
EasyCatalogueRow draggedProduct = categoryProductsTable.getItems().remove(index);
categoryProductsTable.getItems().add(dropIndex, draggedProduct);
dropIndex++;
}
event.setDropCompleted(true);
categoryProductsTable.getSelectionModel().select(null);
event.consume();
updateSortIndicies();
}
});
return row;
});
Ok, so I figured it out. Not sure it's the best way to do it but it works. Basically I added an event listener to the table view which handles the DragOver event. This event is fired whilst dragging the rows within the table view.
Essentially, whilst the drag is being performed, I work out if we need to scroll up or down or not scroll at all. This is done by working out if the items being dragged are within either the upper or lower proximity areas of the table view.
A separate thread controlled by the DragOver event listener then handles the scrolling.
public class CategoryProductsReportController extends ReportController implements Initializable {
#FXML
private TableView<EasyCatalogueRow> categoryProductsTable;
private ObservableList<EasyCatalogueRow> categoryProducts = FXCollections.observableArrayList();
public enum ScrollMode {
UP, DOWN, NONE
}
private AutoScrollableTableThread autoScrollThread = null;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
initProductTable();
}
private void initProductTable() {
categoryProductsTable.setItems(categoryProducts);
...
...
// Multi Row Drag And Drop To Allow Items To Be Re-Positioned Within
// Table
categoryProductsTable.setRowFactory(tv -> {
TableRow<EasyCatalogueRow> row = new TableRow<EasyCatalogueRow>();
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, new ArrayList<Integer>(categoryProductsTable.getSelectionModel().getSelectedIndices()));
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int dropIndex;
if (row.isEmpty()) {
dropIndex = categoryProductsTable.getItems().size();
} else {
dropIndex = row.getIndex();
}
ArrayList<Integer> indexes = (ArrayList<Integer>) db.getContent(SERIALIZED_MIME_TYPE);
for (int index : indexes) {
EasyCatalogueRow draggedProduct = categoryProductsTable.getItems().remove(index);
categoryProductsTable.getItems().add(dropIndex, draggedProduct);
dropIndex++;
}
event.setDropCompleted(true);
categoryProductsTable.getSelectionModel().select(null);
event.consume();
updateSortIndicies();
}
});
return row;
});
categoryProductsTable.addEventFilter(DragEvent.DRAG_DROPPED, event -> {
if (autoScrollThread != null) {
autoScrollThread.stopScrolling();
autoScrollThread = null;
}
});
categoryProductsTable.addEventFilter(DragEvent.DRAG_OVER, event -> {
double proximity = 100;
Bounds tableBounds = categoryProductsTable.getLayoutBounds();
double dragY = event.getY();
//System.out.println(tableBounds.getMinY() + " --> " + tableBounds.getMaxY() + " --> " + dragY);
// Area At Top Of Table View. i.e Initiate Upwards Auto Scroll If
// We Detect Anything Being Dragged Above This Line.
double topYProximity = tableBounds.getMinY() + proximity;
// Area At Bottom Of Table View. i.e Initiate Downwards Auto Scroll If
// We Detect Anything Being Dragged Below This Line.
double bottomYProximity = tableBounds.getMaxY() - proximity;
// We Now Make Use Of A Thread To Scroll The Table Up Or Down If
// The Objects Being Dragged Are Within The Upper Or Lower
// Proximity Areas
if (dragY < topYProximity) {
// We Need To Scroll Up
if (autoScrollThread == null) {
autoScrollThread = new AutoScrollableTableThread(categoryProductsTable);
autoScrollThread.scrollUp();
autoScrollThread.start();
}
} else if (dragY > bottomYProximity) {
// We Need To Scroll Down
if (autoScrollThread == null) {
autoScrollThread = new AutoScrollableTableThread(categoryProductsTable);
autoScrollThread.scrollDown();
autoScrollThread.start();
}
} else {
// No Auto Scroll Required We Are Within Bounds
if (autoScrollThread != null) {
autoScrollThread.stopScrolling();
autoScrollThread = null;
}
}
});
}
}
class AutoScrollableTableThread extends Thread {
private boolean running = true;
private ScrollMode scrollMode = ScrollMode.NONE;
private ScrollBar verticalScrollBar = null;
public AutoScrollableTableThread(TableView tableView) {
super();
setDaemon(true);
verticalScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
}
#Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
while (running) {
Platform.runLater(() -> {
if (verticalScrollBar != null && scrollMode == ScrollMode.UP) {
verticalScrollBar.setValue(verticalScrollBar.getValue() - 0.01);
} else if (verticalScrollBar != null && scrollMode == ScrollMode.DOWN) {
verticalScrollBar.setValue(verticalScrollBar.getValue() + 0.01);
}
});
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void scrollUp() {
System.out.println("Start To Scroll Up");
scrollMode = ScrollMode.UP;
running = true;
}
public void scrollDown() {
System.out.println("Start To Scroll Down");
scrollMode = ScrollMode.DOWN;
running = true;
}
public void stopScrolling() {
System.out.println("Stop Scrolling");
running = false;
scrollMode = ScrollMode.NONE;
}
}
Table contains the following rows (one column just for example):
A
B
C
I'm trying to figure out how to drag an item into it, and have it placed between existing rows B and C.
I am able to do drag-and-drop that results in an item added at the end of table but I can't figure out how to place it in between rows, based on where I release the mouse button.
Create a rowFactory producing TableRows that accept the gesture and decide by the mouse position, whether to add the item before or after the row:
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
Button button = new Button("A");
// d&d source providing next char
button.setOnDragDetected(evt -> {
Dragboard db = button.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.putString(button.getText());
db.setContent(content);
});
button.setOnDragDone(evt -> {
if (evt.isAccepted()) {
// next char
button.setText(Character.toString((char) (button.getText().charAt(0) + 1)));
}
});
// accept for empty table too
table.setOnDragOver(evt -> {
if (evt.getDragboard().hasString()) {
evt.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
evt.consume();
});
table.setOnDragDropped(evt -> {
Dragboard db = evt.getDragboard();
if (db.hasString()) {
table.getItems().add(new Item(db.getString()));
evt.setDropCompleted(true);
}
evt.consume();
});
TableColumn<Item, String> col = new TableColumn<>("value");
col.setCellValueFactory(new PropertyValueFactory<>("value"));
table.getColumns().add(col);
// let rows accept drop too
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow();
row.setOnDragOver(evt -> {
if (evt.getDragboard().hasString()) {
evt.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
evt.consume();
});
row.setOnDragDropped(evt -> {
Dragboard db = evt.getDragboard();
if (db.hasString()) {
Item item = new Item(db.getString());
if (row.isEmpty()) {
// row is empty (at the end -> append item)
table.getItems().add(item);
} else {
// decide based on drop position whether to add the element before or after
int offset = evt.getY() > row.getHeight() / 2 ? 1 : 0;
table.getItems().add(row.getIndex() + offset, item);
evt.setDropCompleted(true);
}
}
evt.consume();
});
return row;
});
Scene scene = new Scene(new VBox(button, table));
primaryStage.setScene(scene);
primaryStage.show();
}
public class Item {
public Item() {
}
public Item(String value) {
this.value.set(value);
}
private final StringProperty value = new SimpleStringProperty();
public String getValue() {
return value.get();
}
public void setValue(String val) {
value.set(val);
}
public StringProperty valueProperty() {
return value;
}
}
I'm using a TreeTableView to display the content of a tree. The sorting order in the tree is manual, and I want to be able to drag and drop items.
How can I drag and drop items in a TreeTableView?
One way is to us a 'treeTableView.setRowFactory'. In the 'call' method, you create a row, to which you attach the 'onDragDetected', 'onDragDropped' etc. See example below.
// Create the root, RowContainer is your class contianing row attributes
TreeItem<RowContainer> rootTIFX = new TreeItem<RowContainer>(rowContainerRoot);
// Add leaves under your root.
...
// Create the row factory
treeTableView.setRowFactory(new Callback<TreeTableView, TreeTableRow<RowContainer>>() {
#Override
public TreeTableRow<RowContainer> call(final TreeTableView param) {
final TreeTableRow<RowContainer> row = new TreeTableRow<RowContainer>();
row.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// drag was detected, start drag-and-drop gesture
TreeItem<RowContainer> selected = (TreeItem<RowContainer>) treeTableView.getSelectionModel().getSelectedItem();
// to access your RowContainer use 'selected.getValue()'
if (selected != null) {
Dragboard db = treeTableView.startDragAndDrop(TransferMode.ANY);
// create a miniature of the row you're dragging
db.setDragView(row.snapshot(null, null));
// Keep whats being dragged on the clipboard
ClipboardContent content = new ClipboardContent();
content.putString(selected.getValue().getName());
db.setContent(content);
event.consume();
}
}
});
row.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
// data is dragged over the target
Dragboard db = event.getDragboard();
if (event.getDragboard().hasString()){
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
}});
row.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
Dragboard db = event.getDragboard();
boolean success = false;
if (event.getDragboard().hasString()) {
if (!row.isEmpty()) {
// This is were you do your magic.
// Move your row in the tree etc
// Here is two examples of how to access
// the drop destination:
int dropIndex = row.getIndex();
TreeItem<RowContainer> droppedon = row.getTreeItem();
success = true;
}
}
event.setDropCompleted(success);
event.consume();
}});
return row;
}
});
I have a TreeView, with many TreeItem. what i want to do is that i enable modification for the selected TreeItem and disable it for others.
To get all the TreeView to be modifiable i use :
syTree.setEditable(true);
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
}
syTree.setOnEditCommit(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
syTree.getRoot().getChildren().set(syTree.getRow(t.getTreeItem()), new TreeItem<String>(t.getNewValue()));
System.out.println("setOnEditCommit");
//}
}
});
syTree.setOnEditCancel(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
System.out.println("setOnEditCancel");
}
});
This line just change all the TreeItems to TextField when trying to modify :
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
How to do it for a specific TreeItem ?
Any help please ?
I think you are saying you want some cells to be editable, and some not to be editable, depending on some condition on the TreeItem they are displaying. If so, it's possible, you just need to do a little more work with your cell factory:
Callback<TreeView<String>, TreeCell<String>> defaultCellFactory = TextFieldTreeCell.forTreeView();
syTree.setCellFactory((TreeView<String> tv) -> {
TreeCell<String> cell = defaultCellFactory.call(tv);
cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
if (newTreeItem == null) {
cell.setEditable(false);
} else if ( /* newTreeItem should be editable */) {
cell.setEditable(true);
} else {
cell.setEditable(false);
}
});
return cell ;
});