Drag and drop item into TableView, between existing rows - javafx

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;
}
}

Related

search a word by key enter

i have a problem with my searching method.
With this method, I can enter a word in the textfield and display the word in the textarea. However, this only happens once if i let it run. I need to expand it so, that every time I click on "enter," the program should continue with searching in the textarea. How can i do this?
And please give me code examples. i have only 2 days left for my presentation.
Thanks a lot for the helps
textfield.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.ENTER) {
String text = textarea.getText();
Labeled errorText = null;
if (textfield.getText() != null && !textfield.getText().isEmpty()) {
index = textarea.getText().indexOf(textfield.getText());
textarea.getText();
if (index == -1) {
errorText.setText("Search key Not in the text");
} else {
// errorText.setText("Found");
textarea.selectRange(index, index + textfield.getLength());
}
}
}
}
});
There's an overloaded version of the indexOf method allowing you to search starting at a specific index. Keep track of the index of your last find and start searching from this position:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
textField.setOnAction(evt -> {
String searchText = textField.getText();
if (searchText.isEmpty()) {
return; // searching for empty text doesn't make sense
}
int index = textarea.getSelection().getEnd();
// in case of the first search, start at the beginning
// TODO: adjust condition/starting index according to needs
if (textarea.getSelection().getLength() == 0) {
index = 0;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
textarea.selectRange(newStartIndex, newStartIndex + searchText.length());
}
});
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}
Edit
If you are not satisfied with searching the element after the selection ( or after the cursor, if there is no range selected), you could save the data of the end of the last match:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
class SearchHandler implements EventHandler<ActionEvent> {
int index = 0;
#Override
public void handle(ActionEvent event) {
String searchText = textField.getText();
String fullText = textarea.getText();
if (index + searchText.length() > fullText.length()) {
// no more matches possible
// TODO: notify user
return;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
index = newStartIndex + searchText.length();
textarea.selectRange(newStartIndex, index);
} else {
index = fullText.length();
// TODO: notify user
}
}
}
SearchHandler handler = new SearchHandler();
textField.setOnAction(handler);
// reset index to search from start when changing the text of the TextField
textField.textProperty().addListener((o, oldValue, newValue) -> handler.index = 0);
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX Auto Scroll Table Up or Down When Dragging Rows Outside Of Viewport

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;
}
}

javafx previous tabs affect newest tab

Everything works fine until I create a new tab. Then when I go to the previous and try to use any of the buttons they affect the latest tab not the one I have selected. But if I go to the latest tab it works like normal. Here is the class that I use to make my tabs. So, why is the previous tabs affecting the lastest? And how do I fix it?
public class JTab {
private javafx.scene.control.Tab tab;
private ImageView imgView;
private Image logo;
private BorderPane root;
private Button reloadButton, backButton, forwardButton;
private TextField field;
private WebView view;
private WebEngine engine;
private static JTab instance;
private JBrowser jBrowser;
private JTab(JBrowser jBrowser) {
this.jBrowser = jBrowser;
}
public static JTab getInstance(JBrowser browser) {
if(instance == null)
instance = new JTab(browser);
return instance;
}
public javafx.scene.control.Tab addTab() {
tab = new Tab();
tab.setText("New Tab");
tab.setOnClosed(event2 -> {
if(jBrowser.getTabPane().getTabs().size() == 1) {
jBrowser.getTabPane().setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
}
});
logo = new Image("unknown-document.png");
imgView = new ImageView(logo);
tab.setGraphic(imgView);
HBox hBox = new HBox(5);
hBox.setAlignment(Pos.CENTER);
reloadButton = new Button("Reload");
backButton = new Button("<");
forwardButton = new Button(">");
reloadButton.setOnAction(event1 -> engine.reload());
backButton.setOnAction(event1 -> loadData(goBack()));
forwardButton.setOnAction(event1 -> loadData(goForward()));
//The TextField for entering web addresses.
field = new TextField("Enter URL");
field.setPrefColumnCount(50); //make the field at least 50 columns wide.
field.focusedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { //When click on field entire thing selected
Platform.runLater(() -> {
if (field.isFocused() && !field.getText().isEmpty()) {
field.selectAll();
}
});
});
field.setOnKeyPressed(event -> { //When ENTER is pressed it will load page
if (event.getCode() == KeyCode.ENTER) {
if (!field.getText().isEmpty()) {
loadData(field.getText());
}
}
});
//Add all out navigation nodes to the vbox.
hBox.getChildren().addAll(backButton, forwardButton, reloadButton, field);
view = new WebView();
engine = view.getEngine();
engine.setJavaScriptEnabled(true);
engine.getLoadWorker().stateProperty().addListener(
(ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
tab.setText(getTitle());
//TODO setGraphic
}
});
loadData("google.com");
root = new BorderPane();
root.setPrefSize(1024, 768);
root.setTop(hBox);
root.setCenter(view);
tab.setContent(root);
return tab;
}
public void loadData(String URL) {
if(!URL.startsWith("http://")) {
URL = "http://" + URL;
}
field.setText(URL);
tab.setText(URL);
engine.load(URL);
}
private String getTitle() {
Document doc = engine.getDocument();
NodeList heads = doc.getElementsByTagName("head");
String titleText = engine.getLocation() ; // use location if page does not define a title
if (heads.getLength() > 0) {
Element head = (Element)heads.item(0);
NodeList titles = head.getElementsByTagName("title");
if (titles.getLength() > 0) {
Node title = titles.item(0);
titleText = title.getTextContent();
}
}
return titleText;
}
private String goBack() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList = history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(-1));
return entryList.get(currentIndex>0?currentIndex-1:currentIndex).getUrl();
}
private String goForward() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList=history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(1));
return entryList.get(currentIndex<entryList.size()-1?currentIndex+1:currentIndex).getUrl();
}
}
Remove getInstance(jBrowser) method
Make the constructor public.
Then to add a tab to a tabPane do
tabPane.getTabs().add(new JTab(jBrowser).addTab());

TreeTableView drag and drop

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;
}
});

Drag BorderPane body

I use this JavaFX code to drag BorderPane into FlowPane:
private Node dragPanel(Node bp)
{
bp.setOnDragDetected(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
});
bp.setOnDragOver(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean accept = true;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
int myIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
if (incomingIndex == myIndex)
{
accept = false;
}
}
catch (java.lang.NumberFormatException e)
{
// handle null or not number string in clipboard
accept = false;
}
}
else
{
accept = false;
}
if (accept)
{
event.acceptTransferModes(TransferMode.MOVE);
}
}
});
bp.setOnDragDropped(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean success = false;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
final Pane parent = (Pane) bp.getParent();
final ObservableList<Node> children = parent.getChildren();
int myIndex = children.indexOf(bp);
final int laterIndex = Math.max(incomingIndex, myIndex);
Node removedLater = children.remove(laterIndex);
final int earlierIndex = Math.min(incomingIndex, myIndex);
Node removedEarlier = children.remove(earlierIndex);
children.add(earlierIndex, removedLater);
children.add(laterIndex, removedEarlier);
success = true;
}
catch (java.lang.NumberFormatException e)
{
//TO DO... handle null or not number string in clipboard
}
}
event.setDropCompleted(success);
}
});
// bp.setMinSize(50, 50);
return bp;
}
I enable this drag event using this code:
BorderPane panel = new BorderPane();
dragPanel(panel),
I also have resize code which is also activated. I need some way to apply the drag code only of I click and drag the panel. I want to disable the drag listener when I drag the panel borders. Is there a way to limit this?
I'm guessing by "borders" you just mean the edges of the border panes. You can just check the coordinates of the mouse event and only initiate dragging if you're away from the borders. To do this, you need to know the width and height of the border pane. The methods to get those are defined in Region, so you need to narrow the type of the parameter from Node to Region. This will still work if you call dragPanel(panel) but you won't be able to pass in a Node that is not a Region instance.
final int borderSize = 5 ;
// ...
private Node dragPane(Region bp) {
bp.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double x = event.getX();
double y = event.getY();
double width = bp.getWidth();
double height = bp.getHeight();
if (x > borderSize && x < width - borderSize
&& y > borderSize && y < height - borderSize) {
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
}
});
// ...
}

Resources