Отображать TableView‹ObservableList‹String›› внутри TreeView‹E›

Дизайн, который я имел в виду, примерно такой: есть TreeView<E>, где E — это класс-оболочка, реализация которого может выглядеть так:

public class E {
    private int key;
    private String value;

    public E(int key, String value) {
        this.key = key;
        this.value = value;
    }

    public int getKey(){
        return key; 
    }

    public String getValue() {
        return value;
    }

    public String toString(){
        return value;
    }
}

Итак, TreeView<E> будет содержать кучу TreeItem<E> экземпляров. И метод toString() E отображает значение.

И когда вы нажимаете любой из элементов дерева, я хочу, чтобы TableView<ObservableList<String>> отображалось под выбранным TreeItem<E>, содержащим все строки из БД, которая связана со значением ключа TreeItem<E>.

Это возможно? Я знаю, что TreeTableView — достойный вариант, но я не хочу показывать какие-либо столбцы, пока не нажму один из элементов дерева. И я хочу, чтобы у каждого TreeItem<E> была своя таблица. Хотя, для справки, все таблицы будут иметь одинаковые столбцы, просто разное количество строк.

Обновление: с помощью @Uluk Biy мне удалось реализовать решение, но оно содержит одну ошибку.

Проблема в том, что когда я нажимаю TreeItem в TreeView, выбирается узел, который находится на два шага ниже первого узла, который не отображается в области прокрутки, когда я нажимаю TreeItem (TreeView автоматически содержит панель прокрутки). ). Поэтому, если я растяну окно так, чтобы было скрыто менее 3 элементов дерева, ничего не будет отображаться. И дело не в том, что TableView отображается не в том месте, проблема в том, что не выбран правильный элемент дерева (я знаю это, потому что распечатал значение ключа.

Вот SSCCE, который показывает эту ошибку. Выберите обертку 1 в таблице, и обертка 6 будет выбрана. Если вы измените размер TreeView так, чтобы было скрыто менее трех элементов TreeItem, ничего не будет выбрано.

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class SSCCE extends Application {

    private Stage primaryStage;
    private AnchorPane rootLayout;
    private TreeView<Wrapper> overview; 

    public void start(Stage primaryStage) {
        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("SSCCE");

        initRootLayout();
        showTreeView();
    }

    public void initRootLayout() {
        rootLayout = new AnchorPane();
        rootLayout.setPrefSize(300, 200);

        Scene scene = new Scene(rootLayout);
        primaryStage.setScene(scene);

        primaryStage.show();
    }       

    ObservableList<String> columns = FXCollections.observableArrayList();
    ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
    ObservableList<Wrapper> items = FXCollections.observableArrayList();

    private void showTreeView() {

        // Dummy values
        columns.addAll("key", "text");              
        data.add(FXCollections.observableArrayList("1", "test"));
        data.add(FXCollections.observableArrayList("1", "test2"));
        data.add(FXCollections.observableArrayList("2", "test3"));
        data.add(FXCollections.observableArrayList("2", "test4"));
        data.add(FXCollections.observableArrayList("3", "test5"));
        data.add(FXCollections.observableArrayList("3", "test6"));
        data.add(FXCollections.observableArrayList("4", "test7"));
        data.add(FXCollections.observableArrayList("4", "test8"));
        data.add(FXCollections.observableArrayList("5", "test9"));
        data.add(FXCollections.observableArrayList("5", "test10"));
        data.add(FXCollections.observableArrayList("6", "test11"));
        data.add(FXCollections.observableArrayList("6", "test12"));
        items.addAll(new Wrapper(1, "wrapper 1"), new Wrapper(2, "wrapper 2"), new Wrapper(3, "wrapper 3"), new Wrapper(4, "wrapper 4"), new Wrapper(5, "wrapper 5"), new Wrapper(6, "wrapper 6"));

        TreeItem<Wrapper> root = new TreeItem<Wrapper>();
        overview = new TreeView<Wrapper>(root);
        overview.setPrefHeight(75);

        overview.setCellFactory(new Callback<TreeView<Wrapper>, TreeCell<Wrapper>>() {
            @Override
            public TreeCell<Wrapper> call(TreeView<Wrapper> stringTreeView) {
                TreeCell<Wrapper> treeCell = new TreeCell<Wrapper>() {
                    @Override
                    protected void updateItem(Wrapper item, boolean empty) {
                        super.updateItem(item, empty);
                        if (empty || item == null) {
                            setText(null);
                            setGraphic(null);
                        } else {
                            if (getTreeItem().isLeaf() && isSelected()) {
                                setText(null);

                                // A debug text that proves that the wrong item is being selected.
                                System.out.println("Selected wrapper: " + item);

                                TableView<ObservableList<String>> table = new TableView<ObservableList<String>>();
                                for (int i = 0; i < columns.size(); i++) {
                                    final int j = i;

                                    TableColumn<ObservableList<String>, String> column = new TableColumn<ObservableList<String>, String>(
                                            columns.get(i));

                                    column.setCellValueFactory((
                                            TableColumn.CellDataFeatures<ObservableList<String>, String> param) -> new SimpleStringProperty(
                                            param.getValue().get(j)
                                                    .toString()));

                                    table.getColumns()
                                            .add(column);
                                }

                                ObservableList<ObservableList<String>> selected_data = FXCollections
                                        .observableArrayList();

                                for(int i = 0; i < data.size(); i++) {

                                    if(Integer.parseInt(data.get(i).get(0)) == item.getKey()) {
                                        selected_data.add(data.get(i));
                                    }
                                }
                                table.setItems(selected_data);

                                VBox vbox = new VBox(new Label(item
                                        .getValue()), table);
                                setGraphic(vbox);
                            } else {
                                setText(item.getValue());
                                setGraphic(null);
                            }
                        }
                    }
                };
                return treeCell;
            }
        });

        for (int i = 0; i < items.size(); i++) {
            TreeItem<Wrapper> target = new TreeItem<Wrapper>(items.get(i));
            root.getChildren().add(target);
        }

        overview.setShowRoot(false);
        rootLayout.getChildren().add(overview);

    }

    public static void main(String[] args) {
        launch(args);
    }
}

class Wrapper {
    private int key;
    private String value;

    Wrapper(int key, String value) {
        this.key = key;
        this.value = value;
    }

    int getKey() {
        return key;
    }

    String getValue() {
        return value;
    }

    public String toString() {
        return "Wrapper " + key;
    }
}

person Jonatan Stenbacka    schedule 01.06.2015    source источник
comment
Я предполагаю, что таблица будет отображаться только на листовых узлах, верно? Должна ли таблица снова скрываться при выборе другого элемента дерева? Почему бы не отображать табличное представление только сбоку от древовидного представления, например, представление основных сведений?   -  person Uluk Biy    schedule 01.06.2015
comment
Да. Таблица должна быть снова скрыта при выборе другого элемента дерева. Вы имеете в виду, например, два отдельных окна, где левое показывает древовидное представление, а правое - табличное? Это вариант, такой же, как использование TreeTableView может быть вариантом. Но это будет компромисс с точки зрения дизайна. Но с учетом сказанного: если у меня нет способа реализовать именно то, что я хочу, мне, возможно, придется пойти на компромисс.   -  person Jonatan Stenbacka    schedule 01.06.2015


Ответы (1)


Настройка фабрики пользовательских ячеек здесь не подходит. Поскольку ячейки повторно используются для рендеринга нескольких элементов, поэтому, если изначально все ячейки видны в области просмотра дерева, просто выбор элемента по умолчанию не приведет к повторному использованию ячейки, поэтому не будет вызываться updateItem(). С другой стороны, если за пределами области просмотра есть другие элементы, и пользователь прокручивает их вниз, то ячейка верхнего невидимого элемента будет повторно использоваться для рендеринга ниже тех, которые недавно вошли в область просмотра. Это imo является объяснением поведения, описанного в комментарии ниже. Короче говоря, определение пользовательского TreeCell не является правильным подходом, вместо этого мы можем наблюдать за изменениями выбора элементов и устанавливать там графику:

treeView.getSelectionModel().selectedItemProperty().addListener( ( ObservableValue<? extends TreeItem<Wrapper>> observable,
        TreeItem<Wrapper> oldValue, TreeItem<Wrapper> newValue ) ->
        {
            System.out.println( "Selected newValue: " + newValue );

            if ( oldValue != null )
            {
                // hide graphic of previous selected item
                oldValue.setGraphic( null );
            }

            if ( newValue != null )
            {
                TableView<ObservableList<String>> table = new TableView<>();
                for ( int i = 0; i < columns.size(); i++ )
                {
                    final int j = i;

                    TableColumn<ObservableList<String>, String> column = new TableColumn<>(
                            columns.get( i ) );

                    column.setCellValueFactory( (
                                    TableColumn.CellDataFeatures<ObservableList<String>, String> param ) -> 
                            new SimpleStringProperty( param.getValue().get( j ) ) );

                    table.getColumns().add( column );
                }

                ObservableList<ObservableList<String>> selected_data = FXCollections
                .observableArrayList();

                for ( int i = 0; i < data.size(); i++ )
                {
                    if ( Integer.parseInt( data.get( i ).get( 0 ) ) == newValue.getValue().getKey() )
                    {
                        selected_data.add( data.get( i ) );
                    }
                }
                table.setItems( selected_data );
                VBox vbox = new VBox( new Label( newValue.getValue().getValue() ), table );
                newValue.setGraphic( vbox );
            }
        } );

и не для установки treeView.setCellFactory(...). Теперь проблема в том, как скрыть текст элемента.

person Uluk Biy    schedule 01.06.2015
comment
Спасибо! Но я получаю много ошибок, пытаясь использовать этот фрагмент кода, поскольку в E нет методов setGraphics() или isSelected(). Нужно ли мне, чтобы E расширял интерфейс TableCell или что-то в этом роде, а затем предоставлял ему эти методы? - person Jonatan Stenbacka; 01.06.2015
comment
E — ваш класс, определенный в вопросе: TreeView‹E› treeView = new TreeView‹E›(rootItem); - person Uluk Biy; 01.06.2015
comment
Извините, дальнейшая помощь была бы хороша! Проблема в том, что когда я пытаюсь реализовать приведенный выше пример, он говорит, что TreeCell не содержит никакого метода setGraphics(). Возможно, вы имели в виду setGraphic()? В нем также говорится, что класс TreeItem не содержит метод isSelected(), которого нет в документации Oracle. - person Jonatan Stenbacka; 01.06.2015
comment
@JonatanStenbacka это моя вина. Обновлено. - person Uluk Biy; 01.06.2015
comment
Хорошо, теперь я получаю правильный результат. Единственная проблема заключается в том, что когда я нажимаю TreeItem в TreeView, узел, который показывает TableView, является узлом, который находится на два шага ниже первого узла, который не отображается в области прокрутки, когда я нажимаю TreeItem (TreeView автоматически содержит прокрутку панель). Поэтому, если я растяну окно так, чтобы было скрыто менее 3 элементов дерева, ничего не будет отображаться. И дело не в том, что TableView отображается не в том месте, проблема в том, что не выбран правильный элемент дерева (я знаю это, потому что распечатал значение ключа. Большое спасибо за вашу помощь! - person Jonatan Stenbacka; 01.06.2015
comment
Поэтому, когда я нажимаю определенный TreeItem, тот, который получает isSelected() == true, не тот, который я на самом деле выбираю, а вместо этого TreeItem, который находится на два места ниже первого, который не виден. И если я растяну окно так, что будет скрыто менее трех элементов, не имеет значения, растяну ли я его обратно. Ничего не покажет. Должны ли мы перенести это обсуждение в чат, кстати? - person Jonatan Stenbacka; 01.06.2015
comment
Что ж, @JonatanStenbacka на данном этапе более полезно, если вы предоставите MVCE (SSCCE), который демонстрирует проблему, описанную в ваших комментариях выше. К вашему сведению, поскольку ячейки повторно используются для отображения элементов дерева, важно обратить внимание на код updateItem(). - person Uluk Biy; 01.06.2015
comment
Хорошо. Я добавил SSCCE выше! Это демонстрирует проблему. - person Jonatan Stenbacka; 02.06.2015
comment
@JonatanStenbacka с использованием setCellFactory(...) было неправильным подходом. вместо этого попробуйте использовать прослушиватель изменений selectedItem. Но у него есть проблема, когда в слушателе вы не можете напрямую изменить текст элемента. - person Uluk Biy; 02.06.2015