Вы совершаете грех преждевременной оптимизации :).
TreeCell
s по существу, только для видны в настоящее время элементов в TreeView
. Когда вы расширяете или сворачиваете узлы в дереве или когда вы прокручиваете, эти TreeCell
s повторно используются для отображения разных TreeItem
. Это цель updateItem(...)
и аналогичные методы в TreeCell
; они вызывается, когда элемент, отображаемый этим экземпляром TreeCell
, изменяется.
A TreeCell
на моей системе около 1/4 дюйма; для отображения 100 000 TreeCell
s потребуется монитор более 2000 футов/630 метров в высоту. В этот момент у вас, вероятно, возникают более серьезные проблемы с распределением памяти, чем у некоторых дополнительных слушателей ... Но, во всяком случае, слушатель будет вызываться только в том случае, если событие происходит на этой конкретной ячейке и занимает довольно небольшую площадь по сравнению с сама клетка, поэтому, если у вас нет прямых доказательств, регистрирующих слушателей на ячейках (которые, как вы видели, значительно уменьшают сложность кода) отрицательно влияет на производительность, вы должны использовать подход «слушатель на ячейку».
Вот пример дерева, содержащего 1 000 000 Integer
-значных элементов дерева. Он отслеживает количество созданных TreeCell
(в моей системе оно никогда не превышает 20 с установленным размером окна). Он также отображает ярлык; вы можете перетащить значения из дерева на метку, и на этикетке будет отображаться текущее количество значений, отброшенных там.
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TreeViewNoSelection extends Application {
private static int cellCount = 0 ;
private final DataFormat objectDataFormat = new DataFormat("application/x-java-serialized-object");
@Override
public void start(Stage primaryStage) {
TreeView<Integer> tree = new TreeView<>();
tree.setShowRoot(false);
Task<TreeItem<Integer>> buildTreeTask = new Task<TreeItem<Integer>>() {
@Override
protected TreeItem<Integer> call() throws Exception {
TreeItem<Integer> treeRoot = new TreeItem<>(0);
IntStream.range(1, 10).mapToObj(this::createItem)
.forEach(treeRoot.getChildren()::add);
return treeRoot ;
}
private TreeItem<Integer> createItem(int value) {
TreeItem<Integer> item = new TreeItem<>(value);
if (value < 100_000) {
for (int i = 0; i < 10; i++) {
item.getChildren().add(createItem(value * 10 + i));
}
}
return item ;
}
};
tree.setCellFactory(tv -> new TreeCell<Integer>() {
{
System.out.println("Cells created: "+(++cellCount));
setOnDragDetected(e -> {
if (! isEmpty()) {
Dragboard db = startDragAndDrop(TransferMode.COPY);
ClipboardContent cc = new ClipboardContent();
cc.put(objectDataFormat, getItem());
db.setContent(cc);
Label label = new Label(String.format("Add %,d", getItem()));
new Scene(label);
db.setDragView(label.snapshot(null, null));
}
});
}
@Override
public void updateItem(Integer value, boolean empty) {
super.updateItem(value, empty);
if (empty) {
setText(null);
} else {
setText(String.format("%,d", value));
}
}
});
IntegerProperty total = new SimpleIntegerProperty();
Label label = new Label();
label.textProperty().bind(total.asString("Total: %,d"));
label.setOnDragOver(e ->
e.acceptTransferModes(TransferMode.COPY));
// in real life use a CSS pseudoclass and external CSS file for the background:
label.setOnDragEntered(e -> label.setStyle("-fx-background-color: yellow;"));
label.setOnDragExited(e -> label.setStyle(""));
label.setOnDragDropped(e -> {
Dragboard db = e.getDragboard();
if (db.hasContent(objectDataFormat)) {
Integer value = (Integer) db.getContent(objectDataFormat);
total.set(total.get() + value);
e.setDropCompleted(true);
}
});
BorderPane.setMargin(label, new Insets(10));
label.setMaxWidth(Double.MAX_VALUE);
label.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(new Label("Loading..."));
buildTreeTask.setOnSucceeded(e -> {
tree.setRoot(buildTreeTask.getValue());
root.setCenter(tree);
root.setBottom(label);
});
primaryStage.setScene(new Scene(root, 250, 400));
primaryStage.show();
Thread t = new Thread(buildTreeTask);
t.setDaemon(true);
t.start();
}
public static void main(String[] args) {
launch(args);
}
}
Для вопроса выбора: я бы поставил под вопрос, почему вы хотите это сделать; это создаст необычный пользовательский интерфейс. Вероятно, проблема заключается в том, что обработчики событий, «обработанные», которые управляют выбором, вызывается перед обработчиками, которые вы определяете, поэтому к тому времени, когда вы потребляете событие, выбор уже изменен. Вы можете попробовать добавить фильтр событий вместо:
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, Event::consume);
, но это будет также отключить расширение/разрушения узлов в дереве.
Таким образом, вы можете попробовать что-то вроде:
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (getTreeItem() != null) {
Object target = e.getTarget();
if (target instanceof Node && ((Node)target).getStyleClass().contains("arrow")) {
getTreeItem().setExpanded(! getTreeItem().isExpanded());
}
}
e.consume();
});
в этот момент он начинает выглядеть как что-то хак ...
Если вы хотите полностью отключить выбор, другой вариант может быть создать пользовательскую модель выбора для дерева, которое всегда возвращает пустой выбор.
Рассмотрите [Перетаскивание из списка в TreeView] (http://stackoverflow.com/questions/30453919/drag-and-drop-from-listview-to-treeview) –
Определенно интересно, я просмотрю это позже (Мне это может понадобиться когда-нибудь!) К счастью, сейчас я перетаскиваю на холст, и для этого у меня уже есть код для определения моей цели. –