2015-06-18 4 views
1

Для приложения, которое я разрабатываю, у меня есть TreeView с (моим собственным типом) TreeItems. Это работает отлично, и я получаю элементы для отображения, как ожидалось.JavaFX: как обрабатывать перетаскивание элемента из TreeView

Теперь я хочу иметь возможность обрабатывать перетаскивание элемента из этого TreeView в другую часть окна приложения и выполнять его там. Я столкнулся с двумя (по крайней мере ...) вопросами:

  1. Всякий раз, когда вы нажимаете в TreeView, элемент всегда выбирается. Можно ли это предотвратить?
  2. При добавлении слушателя MouseEvent в TreeView я получаю события, с которыми я мог бы обнаружить перетаскивание и ответить на это. Я, однако, не смог определить соответствующий TreeItem для события мыши. Мне нужно знать точный TreeItem, конечно, за то, что он работает. Это возможно?

Некоторые вещи я пытался:

  1. Я добавил свой собственный мобильный завод и даже при обработке и потребляя все события мыши на ячейку, элемент в дереве еще выбранные ???
  2. Если я добавлю обработчик MouseEvent в каждую ячейку, я смогу управлять перетаскиванием, но при условии, что в TreeView могут быть тысячи (потенциально >> 100 000, не все расширенные) строк, isn «Это огромные накладные расходы, и не лучше ли иметь один обработчик событий для TreeView? (Но тогда, как я могу определить соответствующий TreeItem?)

События TreeView мыши дают мне следующую информацию:

Нет клетки не щелкнул: MouseEvent [source = TreeView[id=templateTreeView, styleClass=tree-view], target = [email protected][styleClass=cell indexed-cell tree-cell]'null', eventType = MOUSE_PRESSED, consumed = false, x = 193.0, y = 289.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = [email protected][styleClass=cell indexed-cell tree-cell]'null', point = Point3D [x = 192.0, y = 8.0, z = 0.0], distance = 1492.820323027551]

Cell с текстом "Атрибуты" щелкнул: MouseEvent [source = TreeView[id=templateTreeView, styleClass=tree-view], target = [email protected][styleClass=cell indexed-cell tree-cell]'Attributes', eventType = MOUSE_PRESSED, consumed = false, x = 76.0, y = 34.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = [email protected][styleClass=cell indexed-cell tree-cell]'Attributes', point = Point3D [x = 75.0, y = 13.0, z = 0.0], distance = 1492.820323027551]

Я предполагаю, что секрет находится где-то в узле PickResult, но оттуда я все еще не могу понять, как добраться до TreeItem.

Надежда есть (легкий) ответ на этот вопрос ...

+1

Рассмотрите [Перетаскивание из списка в TreeView] (http://stackoverflow.com/questions/30453919/drag-and-drop-from-listview-to-treeview) –

+0

Определенно интересно, я просмотрю это позже (Мне это может понадобиться когда-нибудь!) К счастью, сейчас я перетаскиваю на холст, и для этого у меня уже есть код для определения моей цели. –

ответ

3

Вы совершаете грех преждевременной оптимизации :).

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

в этот момент он начинает выглядеть как что-то хак ...

Если вы хотите полностью отключить выбор, другой вариант может быть создать пользовательскую модель выбора для дерева, которое всегда возвращает пустой выбор.

+0

Я собрал пример с использованием модели выбора, но я не уверен, что полностью отключить выделение было то, что вы хотели. Я могу опубликовать это, если будет полезно. –

+0

Удивительно, спасибо за ваш обширный ответ! Мне нравится, что «совершение греха преждевременной оптимизации»! Таким образом, решение было действительно просто сделать это с TreeCells. На самом деле это сделало все довольно просто! У меня теперь есть перетаскивание. У пользователя отсутствует визуальное подтверждение пользователю, что он что-то перетаскивает, но я думаю, что это должно быть легко выполнимо (любые предложения?) –

+0

Что касается нежелательного выбора, это было потому, что у меня было изменение модели выбора слушатель, который вызовет изменение, которое я не хотел в этом случае. Но теперь я могу обнаружить узел, который был выбран из обработчика MouseEvent, мне не нужно это делать, и я могу сделать свой код намного чище! –

Смежные вопросы