Деревья

Расмотрим такую математическую структуру как дерево. В JavaFX для работы с деревьями есть компонента TreeView. А дерево представляет собой набор связанных узлов.

Создание дерева

Работается с деревом следующим образом. Добавляем на форму компоненту TreeView

Imgur

получаем какую-то такую разметку

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TreeView?>
<?import javafx.scene.layout.*?>
<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <columnConstraints>
      <ColumnConstraints />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
      <RowConstraints />
   </rowConstraints>
   <children>
      <VBox>
         <children>
            <TreeView fx:id="mainTree" prefHeight="368.0" prefWidth="593.0" />
         </children>
         <padding>
            <Insets bottom="4.0" left="4.0" right="4.0" top="4.0" />
         </padding>
      </VBox>
   </children>
</GridPane>

если мы хотим отобразить какую-нибудь структуру, мы делаем следующим образом:

public class Controller implements Initializable {
    @FXML
    TreeView<Integer> mainTree;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        TreeItem<Integer> root = new TreeItem<>(40);
        TreeItem<Integer> child11 = new TreeItem<>(30);
        TreeItem<Integer> child12 = new TreeItem<>(50);

        // привязываем узлы к корневому узлу
        root.getChildren().add(child11);
        root.getChildren().add(child12);

        // передаем корневой узел в компоненту
        mainTree.setRoot(root);

        // раскрываем узел
        root.setExpanded(true);
    }
}

получим такую картинку

Imgur

мы можем добавлять узлы и к потомкам, по аналогии как было с root, например:

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    TreeItem<Integer> root = new TreeItem<>(40);
    TreeItem<Integer> child11 = new TreeItem<>(30);
    TreeItem<Integer> child12 = new TreeItem<>(50);

    // добавили подузлы к узлам
    child11.getChildren().add(new TreeItem<>(33));
    child11.getChildren().add(new TreeItem<>(37));
    child11.setExpanded(true);

    root.getChildren().add(child11);
    root.getChildren().add(child12);

    mainTree.setRoot(root);
    root.setExpanded(true);
}

получится вот так:

Imgur

Реакция на клик

допустим мы хотим чтобы при выборе узла выводилась информация о нем. В этом случае мы должны добавить обработчик события изменения выбраного значения делается это так

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    // ...
    root.setExpanded(true);
    
    // добавляем реакцию на клик по узлам
    mainTree.getSelectionModel().selectedItemProperty().addListener((changed, oldNode, newNode) -> {
        System.out.println(newNode.getValue());
    });
}

Доступ к родителю узла

а допустим захотим узнать значение родительского узла, для этого мы можем воспользоваться свойством getParent(), подправим код:

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    // ...
    root.setExpanded(true);

    mainTree.getSelectionModel().selectedItemProperty().addListener((changed, oldNode, newNode) -> {
        // получаем доступ к родительскому узлу
        TreeItem<Integer> parentNode = newNode.getParent();
        // выводим его значение
        System.out.println(parentNode.getValue());
    });
}

проверяем

Тут у нас кстати ошибка вылетает потому что у корневого узла нет родителя. То есть в идеале писать как-то так:

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    // ...
    root.setExpanded(true);

    mainTree.getSelectionModel().selectedItemProperty().addListener((changed, oldNode, newNode) -> {
        // получаем доступ к родительскому узлу
        TreeItem<Integer> parentNode = newNode.getParent();
        // выводим его значение
        if (parentNode != null) {
            System.out.println(parentNode.getValue());
        }
    });
}

Добавление узла в середину дерева

Допустим мы хотим добавить узел куда-нибудь в середину.

Добавим кнопку

Imgur

в fxml получится вот такая разметка:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TreeView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Button?>
<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <columnConstraints>
      <ColumnConstraints />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
      <RowConstraints />
   </rowConstraints>
   <children>
      <VBox>
         <children>
            <Button onAction="#addNode">Добавить узел</Button>
            <TreeView fx:id="mainTree" prefHeight="368.0" prefWidth="593.0" />
         </children>
         <padding>
            <Insets bottom="4.0" left="4.0" right="4.0" top="4.0" />
         </padding>
      </VBox>
   </children>
</GridPane>

теперь добавим реакцию на кнпоку

public class Controller implements Initializable {
    @FXML
    TreeView<Integer> mainTree;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        // ...
    }
    
    // НОВЫЙ МЕТОД
    public void addNode(ActionEvent actionEvent) {
        // берем первого потомка, то бишь 30
        TreeItem<Integer> child11 = mainTree.getRoot().getChildren().get(0);
        // берем его родителя, то бишь 40
        TreeItem<Integer> parent = child11.getParent();

        // создаем новый узел со значением 0
        TreeItem<Integer> newChild11 = new TreeItem<>(0);
        // удаляем у 40 узел 30
        parent.getChildren().remove(child11);
        // добавляем на место 30 новый узел со значением 0
        parent.getChildren().add(0, newChild11);

        // подклеиваем 30 к новому узлу
        newChild11.getChildren().add(child11);

        // раскрываем новый узел
        newChild11.setExpanded(true);
    }
}

выглядеть это будет вот так:

кстати если понажимать на кнопку по несколько раз, будет все время добавлятся новый узел в одно и тоже место и получится лесенка: