我有一个分层数据树视图,表示从数据库中检索的元素。我希望能够将子(叶)节点从一个父节点移动到另一个父节点,并通过拖放重新排列父节点内的叶节点序列,最后使用移动操作的结果更新数据库。
现在我可以将其中一个叶节点放到父节点上,这允许我将子节点添加到父节点。但是,我真的没有看到如何在同一父节点或不同父节点中的两个其他叶节点之间删除叶节点。
我希望能够做类似以下的事情。从看起来像这样的树开始:
Root
|
+-+-P1
| |
| +--L1a
| +--L1b
|
+-+-P2
|
+--L2a
+--L2b
例如,我希望能够选择L2a
并将其拖放到L1a
和L1b
之间。或L1a
之前或L1b
之后。完成后我想拖动P1
的子节点并通过DND重新排列它们。
部分原因是提供掉落位置的指示。例如,如果您将光标定位在这些节点之间,则L1a
和L1b
之间的一条线。
这可能吗?我在任何地方都没有看到任何这样的例子。
我看到的另一件事是DND的效果不会将叶节点从其原始位置移除,即使在我调用setOnDragDetected
的单元格的cell.startDragAndDrop(TransferMode.MOVE)
方法调用中也是如此。我怎样才能让它发挥作用?
编辑9/1/17
我想出了不删除叶节点的最后一部分。
以下是我用来测试此过程的代码。我有一个接口IStoryItem
,它是由两个子类Story
和Part
实现的(空)接口(只是为了创建一个要测试的层次结构)。 Story
班级有title
(String
)和parts
(Part[]
)字段。 Part
班级有title
(String
)和partNumber
(int
)字段。我有一个Utils
类,它创建了一个Story
个对象数组来填充我的TreeView
实例。
这是我的控制器类。它有点长,需要清理,但显示了我到目前为止所尝试的内容。
public class Controller {
public TreeView<IStoryItem> tv;
private final DataFormat objectDataFormat = new DataFormat("application/x-java-serialized-object");
class StoryRoot implements IStoryItem { }
TreeItem<IStoryItem> rootItem;
@FXML
public void initialize() {
StoryRoot storyRoot = new StoryRoot();
rootItem = new TreeItem<>(storyRoot);
tv.setRoot(rootItem);
// For Drag and Drop:
// - rootItem can only accept Story nodes.
// - Story nodes can only accept Part nodes.
// - Part nodes can't accept any other nodes.
tv.setCellFactory(new Callback<TreeView<IStoryItem>, TreeCell<IStoryItem>>() {
@Override
public TreeCell<IStoryItem> call(TreeView<IStoryItem> siTreeView) {
TreeCell<IStoryItem> cell = new TreeCell<IStoryItem>() {
@Override
protected void updateItem(IStoryItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null) { setText(item.toString()); }
}
};
// The following calls are as outlined in:
// https://docs.oracle.com/javase/8/javafx/events-tutorial/drag-drop.htm#CHDJFJDH
cell.setOnDragDetected((MouseEvent event) -> {
// Don't drag Story nodes.
if (cell.getItem() instanceof Story) return;
// drag was detected, start a drag-and-drop gesture
// allow Move transfer mode only
Dragboard db = cell.startDragAndDrop(TransferMode.MOVE);
// Put the Part on the dragboard
// From: https://stackoverflow.com/a/30916660/780350
ClipboardContent content = new ClipboardContent();
content.put(objectDataFormat, cell.getItem());
db.setContent(content);
event.consume();
});
cell.setOnDragDropped((DragEvent event) -> {
try {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(objectDataFormat)) {
Part droppedPart = (Part)db.getContent(objectDataFormat);
IStoryItem targetStoryItem = cell.getItem();
// Question: How to handle drops between leaf items or
// before the initial leaf or after the final leaf.
if (targetStoryItem instanceof Story) {
Story story = (Story) targetStoryItem;
updateStoryWith(droppedPart, story);
addPartTo(cell.getTreeItem(), droppedPart);
success = true;
}
}
event.setDropCompleted(success);
event.consume();
} catch (Exception e) {
System.out.println(e.getMessage());
}
});
cell.setOnDragDone((DragEvent event) -> {
/*
* the drag and drop gesture ended
* if the data was successfully moved, clear it
*/
if (event.getTransferMode() == TransferMode.MOVE) {
// TODO: remove the part that got moved.
IStoryItem item = cell.getItem();
TreeItem<IStoryItem> ti = cell.getTreeItem();
TreeItem<IStoryItem> pti = ti.getParent();
pti.getChildren().remove(ti);
IStoryItem psi = pti.getValue();
boolean removed = removePartFrom(psi, item);
}
event.consume();
});
return cell;
};
});
tv.getSelectionModel()
.selectedItemProperty()
.addListener((observable, oldValue, newValue) -> inspectObject(newValue.getValue()));;
Story[] stories = Utils.createStories();
for (Story s: stories) {
addStoryToTree(s);
}
}
private void updateStoryWith(Part droppedPart, Story story) {
List<Part> partsList = new ArrayList<>(Arrays.asList(story.parts));
partsList.add(droppedPart);
Part [] newParts = (Part[])partsList.toArray(new Part[partsList.size()]);
int idx = 1;
for (Part part : newParts) {
part.partnumber = idx++;
}
story.parts = newParts;
}
private void inspectObject(Object o) {
if (!(o instanceof IStoryItem)) {
System.out.println(o.getClass().toString());
} else if (o instanceof Story) {
Story s = (Story)o;
System.out.println("Story: " + s.toString());
} else if (o instanceof Part) {
Part s = (Part)o;
System.out.println("Part: " + s.toString());
}
}
void addStoryToTree(Story story) {
if (story.parts.length == 0) return;
TreeItem<IStoryItem> item = new TreeItem<>(story);
rootItem.getChildren().add(item);
for (Part part : story.parts) {
addPartTo(item, part);
}
}
void addPartTo(TreeItem<IStoryItem> storyItem, Part part) {
TreeItem<IStoryItem> partItem = new TreeItem<>(part);
storyItem.getChildren().add(partItem);
}
boolean removePartFrom(IStoryItem si, IStoryItem pi) {
if (!(si instanceof Story)) return false;
if (!(pi instanceof Part)) return false;
Story story = (Story) si;
Part part = (Part) pi;
List<Part> plist = new LinkedList<>(Arrays.asList(story.parts));
if (!plist.contains(part)) return false;
boolean removed = plist.remove(part);
story.parts = plist.toArray(new Part[plist.size()]);
return removed;
}
}