JavaFX странный (ключ)EventBehavior

Поэтому я немного поэкспериментировал с javaFX и натолкнулся на довольно странное поведение, которое может быть связано с TableView#edit() метод.

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

Я постараюсь объяснить все поведение самостоятельно, хотя его проще увидеть самим. В основном события путаются при использовании TableView#edit() метод.

1:

Если вы используете contextMenu для добавления нового элемента, keyEvents для клавиш 'escape' и 'Enter' (и, возможно, клавиш со стрелками, хотя я их сейчас не использую) расходуется до того, как они инициируют события в ячейках. (например, textField и ячейка KeyEvents!) Хотя он запускает keyEvent на родительском узле. (AnchorPane в этом случае).

Теперь я точно знаю, что эти ключи записываются и используются поведением contextMenu по умолчанию. Хотя это не должно происходить, поскольку contextMenu уже скрыто после добавления нового элемента. еще больше textField должен получать события, особенно когда он сфокусирован!

2:

Когда вы используете кнопку в нижней части TableView для добавления нового элемента, keyEvents запускаются на родительском узле (AnchorPane) и ячейке. Хотя textField (даже когда он сфокусирован) вообще не получает keyEvents. Я не могу объяснить, почему TextField не получит никакого события даже при вводе, поэтому я предполагаю, что это будет ошибка?

3:

При редактировании ячейки с помощью двойного щелчка он корректно обновляет edit CellProperty TableView (который я проверяю несколько раз). Хотя, когда начинается редактирование, хотя элемент contextMenu (который вызывает только testEdit() для testpurpose) не корректно обновляет состояние редактирования! Достаточно забавно, что позволяет keyEvents продолжать как обычно, в отличие от ситуаций 1 и 2.

4:

Когда вы редактируете элемент, а затем добавляете элемент (в любом случае это вызовет проблему), он обновляет edit CellProperty в текущей ячейке, хотя при остановке редактирования он каким-то образом возвращается к последней ячейке?!? Это та часть, где происходят забавные вещи, которые я действительно не могу объяснить.

Обратите внимание, что методы startEdit() и cancelEdit() вызываются в странные моменты и в неправильных ячейках!

Прямо сейчас я не понимаю ничего из этой логики. Если это предполагаемое поведение, некоторые объяснения этого будут с благодарностью!

Это пример:

package testpacket;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;



public class EditStateTest extends Application
{
    private static ObservableList<SimpleStringProperty> exampleList = FXCollections.observableArrayList();
    //Placeholder for the button
    private static SimpleStringProperty PlaceHolder = new SimpleStringProperty();

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // basic ui setup
        AnchorPane parent = new AnchorPane();
        Scene scene = new Scene(parent);
        primaryStage.setScene(scene);

        //fill backinglist with data
        for(int i = 0 ; i < 20; i++)
            exampleList.add(new SimpleStringProperty("Hello Test"));
        exampleList.add(PlaceHolder);

        //create a basic tableView
        TableView<SimpleStringProperty> listView = new TableView<SimpleStringProperty>();
        listView.setEditable(true);

        TableColumn<SimpleStringProperty, String> column = new TableColumn<SimpleStringProperty, String>();
        column.setCellFactory(E -> new TableCellTest<SimpleStringProperty, String>());
        column.setCellValueFactory(E -> E.getValue());
        column.setEditable(true);

        // set listViews' backing list
        listView.setItems(exampleList);


        listView.getColumns().clear();
        listView.getColumns().add(column);
        parent.getChildren().add(listView);

        parent.setOnKeyReleased(E -> System.out.println("Parent - KeyEvent"));


        primaryStage.show();
    }

    // basic editable cell example
    public static class TableCellTest<S, T> extends TableCell<S, T>
    {
        // The editing textField.
        protected static Button addButton = new Button("Add");
        protected TextField textField = new TextField();;
        protected ContextMenu menu;


        public TableCellTest()
        {
            this.setOnContextMenuRequested(E -> {
                if(this.getTableView().editingCellProperty().get() == null)
                    this.menu.show(this, E.getScreenX(), E.getScreenY());
            });
            this.menu = new ContextMenu();

            MenuItem createNew = new MenuItem("create New");
            createNew.setOnAction(E -> {
                System.out.println("Cell ContextMenu " + this.getIndex() + " - createNew: onAction");
                this.onNewItem(this.getIndex() + 1);
            });
            MenuItem edit = new MenuItem("edit");
            edit.setOnAction(E -> {
                System.out.println("Cell ContextMenu " + this.getIndex() + " - edit: onAction");
                this.startEdit();
            });

            this.menu.getItems().setAll(createNew, edit);

            addButton.addEventHandler(ActionEvent.ACTION, E -> {
                if(this.getIndex() == EditStateTest.exampleList.size() - 1)
                {
                    System.out.println("Cell " + this.getIndex() + " - Button: onAction");
                    this.onNewItem(this.getIndex());
                }
            });
            addButton.prefWidthProperty().bind(this.widthProperty());

            this.setOnKeyReleased(E -> System.out.println("Cell " + this.getIndex() + " - KeyEvent"));
        }

        public void onNewItem(int index)
        {
            EditStateTest.exampleList.add(index, new SimpleStringProperty("New Item"));
            this.getTableView().edit(index, this.getTableColumn());
            textField.requestFocus();
        }

        @Override
        public void startEdit()
        {
            if (!isEditable()
                    || (this.getTableView() != null && !this.getTableView().isEditable())
                    || (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
                return;

            System.out.println("Cell " + this.getIndex() + " - StartEdit");
            super.startEdit();

            this.createTextField();

            textField.setText((String)this.getItem());
            this.setGraphic(textField);
            textField.selectAll();
            this.setText(null);
        }

        @Override
        public void cancelEdit()
        {
            if (!this.isEditing())
                return;

            System.out.println("Cell " + this.getIndex() + " - CancelEdit");
            super.cancelEdit();

            this.setText((String)this.getItem());
            this.setGraphic(null);
        }

        @Override
        protected void updateItem(T item, boolean empty)
        {
            System.out.println("Cell " + this.getIndex() + " - UpdateItem");
            super.updateItem(item, empty);

            if(empty || item == null)
            {
                if(this.getIndex() == EditStateTest.exampleList.size() - 1)
                {
                    this.setText("");
                    this.setGraphic(addButton);
                }
                else
                {
                    this.setText(null);
                    this.setGraphic(null);
                }
            }
            else
            {
                // These checks are needed to make sure this cell is the specific cell that is in editing mode.
                // Technically this#isEditing() can be left out, as it is not accurate enough at this point.
                if(this.getTableView().getEditingCell() != null 
                        && this.getTableView().getEditingCell().getRow() == this.getIndex())
                {
                    //change to TextField
                    this.setText(null);
                    this.setGraphic(textField);
                }
                else
                {
                    //change to actual value
                    this.setText((String)this.getItem());
                    this.setGraphic(null);
                }
            }
        }

        @SuppressWarnings("unchecked")
        public void createTextField()
        {
            textField.setOnKeyReleased(E -> {
                System.out.println("TextField " + this.getIndex() + " - KeyEvent");
                System.out.println(this.getTableView().getEditingCell());
//              if(this.getTableView().getEditingCell().getRow() == this.getIndex())
                    if(E.getCode() == KeyCode.ENTER)
                    {
                        this.setItem((T) textField.getText());
                        this.commitEdit(this.getItem());
                    }
                    else if(E.getCode() == KeyCode.ESCAPE)
                        this.cancelEdit();
            });
        }
    }
}

Я надеюсь, что кто-то может помочь мне в этом. Если у вас есть предложения / решения или обходные пути для этого, пожалуйста, дайте мне знать! Спасибо за ваше время!

2 ответа

Решение

Это своего рода плакат для мантры Джоша Блоха "Инкапсуляция наследства нарушает". Я имею в виду, что когда вы создаете подкласс существующего класса (TableCell в этом случае), вам нужно много знать о реализации этого класса, чтобы подкласс хорошо играл с суперклассом. Вы делаете много предположений в своем коде о взаимодействии между TableView и его ячейки, которые не соответствуют действительности, и это (наряду с некоторыми ошибками и общими странными реализациями обработки событий в некоторых элементах управления) является причиной того, что ваш код ломается.

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

Во-первых, клетки используются повторно. Это хорошо, потому что при большом объеме данных таблица работает очень эффективно, но усложняется. Основная идея заключается в том, что ячейки по существу создаются только для видимых элементов в таблице. Когда пользователь прокручивает или изменяет содержимое таблицы, ячейки, которые больше не нужны, повторно используются для различных элементов, которые становятся видимыми. Это значительно экономит потребление памяти и процессорного времени (при правильном использовании). Чтобы улучшить реализацию, команда JavaFX намеренно не указывает, как это работает, и как и когда ячейки могут быть повторно использованы. Поэтому вы должны быть осторожны с предположениями о непрерывности полей элемента или индекса ячейки (и наоборот, какая ячейка назначается данному элементу или индексу), особенно если вы изменяете структуру таблицы.

То, что вы в основном гарантированы, это:

  • Каждый раз, когда ячейка используется для другого элемента, updateItem() Метод вызывается до визуализации ячейки.
  • В любое время, когда изменяется индекс ячейки (что может быть из-за того, что элемент вставлен в список или из-за того, что ячейка используется повторно или и то, и другое), updateIndex() Метод вызывается до визуализации ячейки.

Тем не менее, обратите внимание, что в случае, когда оба изменяются, нет никакой гарантии порядка, в котором они будут вызваны. Таким образом, если визуализация вашей ячейки зависит как от элемента, так и от индекса (что имеет место в данном случае: вы проверяете и элемент, и индекс в своем методе updateItem(...)), вы должны убедиться, что ячейка обновляется, когда либо из этих свойств меняются. Лучший способ (imo) для достижения этой цели - создать закрытый метод для выполнения обновления и делегировать ему функции updateItem() и updateIndex(). Таким образом, когда вызывается второй из них, ваш метод обновления вызывается с согласованным состоянием.

Если вы измените структуру таблицы, скажем, добавив новую строку, ячейки нужно будет переставить, и некоторые из них, вероятно, будут повторно использованы для различных элементов (и индексов). Однако эта перестановка происходит только при разметке таблицы, что по умолчанию не произойдет до следующего рендеринга кадра. (Это имеет смысл с точки зрения производительности: представьте, что вы вносите 1000 различных изменений в таблицу в цикле; вы не хотите, чтобы ячейки пересчитывались при каждом изменении, вы просто хотите, чтобы они пересчитывались один раз при следующем отображении таблицы в экран.) Это означает, что если вы добавляете строки в таблицу, вы не можете полагаться на правильность индекса или элемента любой ячейки. Вот почему ваш вызов table.edit(...) сразу после добавления новой строки настолько непредсказуем. Хитрость здесь в том, чтобы форсировать раскладку таблицы, вызывая TableView.layout() после добавления строки.

Обратите внимание, что нажатие "Enter", когда ячейка таблицы находится в фокусе, приведет к тому, что эта ячейка перейдет в режим редактирования. Если вы обрабатываете коммиты в текстовом поле в ячейке с помощью обработчика события освобождения ключа, эти обработчики будут взаимодействовать непредсказуемым образом. Я думаю, именно поэтому вы видите странные эффекты обработки ключей, которые вы видите (также обратите внимание, что текстовые поля потребляют ключевые события, которые они обрабатывают внутри). Обходной путь для этого заключается в использовании обработчика onAction для текстового поля (что в любом случае, возможно, является более семантическим).

Не делайте кнопку статичной (я понятия не имею, почему вы все равно захотите это сделать). "Статический" означает, что кнопка является свойством класса в целом, а не экземпляров этого класса. Таким образом, в этом случае все ячейки имеют ссылку на одну кнопку. Поскольку механизм повторного использования ячеек не указан, вы не знаете, что только одна ячейка будет иметь кнопку, установленную в качестве графика. Это может привести к катастрофе. Например, если вы прокрутите ячейку с кнопкой вне поля зрения, а затем вернетесь к виду, нет гарантии, что эта же ячейка будет использоваться для отображения этого последнего элемента, когда он снова появится в поле зрения. Возможно (я не знаю реализацию), что ячейка, которая ранее отображала последний элемент, остается неиспользованной (возможно, частью контейнера виртуального потока, но обрезанной вне поля зрения) и не обновляется. В этом случае кнопка будет дважды отображаться в графе сцены, что приведет к исключению или к непредсказуемому поведению. В принципе, нет веской причины когда-либо делать узел графа сцены статичным, и здесь это особенно плохая идея.

Чтобы закодировать функциональность, подобную этой, вы должны внимательно прочитать документацию по механизму ячейки и TableView, TableColumn, а также TableCell, В какой-то момент вы можете обнаружить, что вам нужно покопаться в исходном коде, чтобы увидеть, как работают предоставленные реализации ячеек.

Вот (я думаю, я не уверен, что я полностью проверил) рабочая версия того, что, я думаю, вы искали. Я сделал несколько небольших изменений в структуре (нет необходимости StringProperty s как тип данных, String работает нормально, если у вас нет идентичных дубликатов), добавлен обработчик onEditCommit и т. д.

import javafx.application.Application;
import javafx.beans.value.ObservableValueBase;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class TableViewWithAddAtEnd extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<String> table = new TableView<>();
        table.setEditable(true);

        TableColumn<String, String> column = new TableColumn<>("Data");
        column.setPrefWidth(150);
        table.getColumns().add(column);

        // use trivial wrapper for string data:
        column.setCellValueFactory(cellData -> new ObservableValueBase<String>() {
            @Override
            public String getValue() {
                return cellData.getValue();
            }
        });

        column.setCellFactory(col -> new EditingCellWithMenuEtc());

        column.setOnEditCommit(e -> 
            table.getItems().set(e.getTablePosition().getRow(), e.getNewValue()));

        for (int i = 1 ; i <= 20; i++) {
            table.getItems().add("Item "+i);
        }
        // blank for "add" button:
        table.getItems().add("");

        BorderPane root = new BorderPane(table);
        primaryStage.setScene(new Scene(root, 600, 600));
        primaryStage.show();

    }

    public static class EditingCellWithMenuEtc extends TableCell<String, String> {
        private TextField textField ;
        private Button button ;
        private ContextMenu contextMenu ;

        // The update relies on knowing both the item and the index
        // Since we don't know (or at least shouldn't rely on) the order
        // in which the item and index are updated, we just delegate
        // implementations of both updateItem and updateIndex to a general
        // method. This way doUpdate() is always called last with consistent
        // state, so we are guaranteed to be in a consistent state when the
        // cell is rendered, even if we are temporarily in an inconsistent 
        // state between the calls to updateItem and updateIndex.

        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            doUpdate(item, getIndex(), empty);
        }

        @Override
        public void updateIndex(int index) {
            super.updateIndex(index);
            doUpdate(getItem(), index, isEmpty());
        }

        // update the cell. This updates the text, graphic, context menu
        // (empty cells and the special button cell don't have context menus)
        // and editable state (empty cells and the special button cell can't
        // be edited)
        private void doUpdate(String item, int index, boolean empty) {
            if (empty) {
                setText(null);
                setGraphic(null);
                setContextMenu(null);
                setEditable(false);
            } else {
                if (index == getTableView().getItems().size() - 1) {
                    setText(null);
                    setGraphic(getButton());
                    setContextMenu(null);
                    setEditable(false);
                } else if (isEditing()) {
                    setText(null);
                    getTextField().setText(item);
                    setGraphic(getTextField());
                    getTextField().requestFocus();
                    setContextMenu(null);
                    setEditable(true);
                } else {
                    setText(item);
                    setGraphic(null);
                    setContextMenu(getMenu());
                    setEditable(true);
                }
            }
        }

        @Override
        public void startEdit() {
            if (! isEditable() 
                    || ! getTableColumn().isEditable()
                    || ! getTableView().isEditable()) {
                return ;
            }
            super.startEdit();
            getTextField().setText(getItem());
            setText(null);
            setGraphic(getTextField());
            setContextMenu(null);
            textField.selectAll();
            textField.requestFocus();
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            setText(getItem());
            setGraphic(null);
            setContextMenu(getMenu());
        }

        @Override
        public void commitEdit(String newValue) {
            // note this fires onEditCommit handler on column:
            super.commitEdit(newValue);
            setText(getItem());
            setGraphic(null);
            setContextMenu(getMenu());
        }

        private void addNewItem(int index) {
            getTableView().getItems().add(index, "New Item");
            // force recomputation of cells:
            getTableView().layout();
            // start edit:
            getTableView().edit(index, getTableColumn());
        }

        private ContextMenu getMenu() {
            if (contextMenu == null) {
                createContextMenu();
            }
            return contextMenu ;
        }

        private void createContextMenu() {
            MenuItem addNew = new MenuItem("Add new");
            addNew.setOnAction(e -> addNewItem(getIndex() + 1));
            MenuItem edit = new MenuItem("Edit");
            // note we call TableView.edit(), not this.startEdit() to ensure 
            // table's editing state is kept consistent:
            edit.setOnAction(e -> getTableView().edit(getIndex(), getTableColumn()));
            contextMenu = new ContextMenu(addNew, edit);
        }

        private Button getButton() {
            if (button == null) {
                createButton();
            }
            return button ;
        }

        private void createButton() {
            button = new Button("Add");
            button.prefWidthProperty().bind(widthProperty());
            button.setOnAction(e -> addNewItem(getTableView().getItems().size() - 1));
        }

        private TextField getTextField() {
            if (textField == null) {
                createTextField();
            }
            return textField ;
        }

        private void createTextField() {
            textField = new TextField();
            // use setOnAction for enter, to avoid conflict with enter on cell:
            textField.setOnAction(e -> commitEdit(textField.getText()));
            // use key released for escape: note text fields do note consume
            // key releases they don't handle:
            textField.setOnKeyReleased(e -> {
                if (e.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }
            });
        }
    }

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

Моя большая учебная статья дня (свободно обобщенная и слегка расширенная из ответа Джеймса):

view.edit(...) безопасно вызывать, только если все ячейки находятся в стабильном состоянии и целевая ячейка видна. Большую часть времени мы можем форсировать стабильное состояние, вызывая view.layout()

Ниже приведен еще один пример игры:

  • как уже упоминалось в одном из моих комментариев, он отличается от того, что Джеймс запускает редактирование в слушателе для элементов: не всегда может быть лучшим местом, имеет преимущество в одном месте (по крайней мере, в том, что касается мутаций списка).) для вызова макета. Недостатком является то, что мы должны быть уверены, что слушатель viewSkin к элементам вызывается раньше нашего. Чтобы гарантировать это, наш собственный слушатель перезаписывается при смене скина.

  • в качестве упражнения для повторного использования я расширил TextFieldTableCell, чтобы дополнительно обработать кнопку / меню и обновить редактируемость ячейки на основе элемента строки.

  • Есть также кнопки вне таблицы для экспериментов: addAndEdit и scrollAndEdit. Последнее должно продемонстрировать, что "нестабильное состояние ячейки" может быть достигнуто путями, отличными от модификации элементов.

В настоящее время я стремлюсь создать подкласс TableView и переопределить его редактирование (...) для принудительного изменения макета. Что-то вроде:

public static class TTableView<S> extends TableView<S> {

    /**
     * Overridden to force a layout before calling super.
     */
    @Override
    public void edit(int row, TableColumn<S, ?> column) {
        layout();
        super.edit(row, column);
    }

}

Это облегчает нагрузку на клиентский код. Однако им остается убедиться, что целевая ячейка прокручена в видимой области.

Пример:

public class TablePersonAddRowAndEdit extends Application {

    private PersonStandIn standIn = new PersonStandIn();
    private final ObservableList<Person> data =
            // Person from Tutorial - with Properties exposed!
            FXCollections.observableArrayList(
                    new Person("Jacob", "Smith", "jacob.smith@example.com"),
                    new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                    new Person("Ethan", "Williams", "ethan.williams@example.com"),
                    new Person("Emma", "Jones", "emma.jones@example.com"),
                    new Person("Michael", "Brown", "michael.brown@example.com")
                    , standIn
                    );


    private Parent getContent() {

        TableView<Person> table = new TableView<>();
        table.setItems(data);
        table.setEditable(true);

        TableColumn<Person, String> firstName = new TableColumn<>("First Name");
        firstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));

        firstName.setCellFactory(v -> new MyTextFieldCell<>());
        ListChangeListener l = c -> {
            while (c.next()) {
                // true added only
                if (c.wasAdded() && ! c.wasRemoved()) {
                    // force the re-layout before starting the edit
                    table.layout();
                    table.edit(c.getFrom(), firstName);
                    return;
                }
            };
        };
        // install the listener to the items after the skin has registered
        // its own
        ChangeListener skinListener = (src, ov, nv) -> {
            table.getItems().removeListener(l);
            table.getItems().addListener(l);
        };
        table.skinProperty().addListener(skinListener);

        table.getColumns().addAll(firstName);

        Button add = new Button("AddAndEdit");
        add.setOnAction(e -> {
            int standInIndex = table.getItems().indexOf(standIn);
            int index = standInIndex < 0 ? table.getItems().size() : standInIndex;
            index =1;
            Person person = createNewItem("edit", index);
            table.getItems().add(index, person);

        });
        Button edit = new Button("Edit");
        edit.setOnAction(e -> {
            int index = 1;//table.getItems().size() -2;
            table.scrollTo(index);
            table.requestFocus();
            table.edit(index, firstName);
        });
        HBox buttons = new HBox(10, add, edit);
        BorderPane content = new BorderPane(table);
        content.setBottom(buttons);
        return content;
    }

    /**
     * A cell that can handle not-editable items. Has to update its
     * editability based on the rowItem. Must be done in updateItem
     * (tried a listener to the tableRow's item, wasn't good enough - doesn't
     * get notified reliably)
     * 
     */
    public static class MyTextFieldCell<S> extends TextFieldTableCell<S, String> {

        private Button button;

        public MyTextFieldCell() {
            super(new DefaultStringConverter());
            ContextMenu menu = new ContextMenu();
            menu.getItems().add(createMenuItem());
            setContextMenu(menu);
        }

        private boolean isStandIn() {
            return getTableRow() != null && getTableRow().getItem() instanceof StandIn;
        }

        /**
         * Update cell's editable based on the rowItem.
         */
        private void doUpdateEditable() {
            if (isEmpty() || isStandIn()) {
                setEditable(false);
            } else {
                setEditable(true);
            }
        }

        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            doUpdateEditable();
            if (isStandIn()) {
                if (isEditing()) {
                    LOG.info("shouldn't be editing - has StandIn");
                }
                if (button == null) {
                    button = createButton();
                }
                setText(null);
                setGraphic(button);
            } 
        }

        private Button createButton() {
            Button b = new Button("Add");
            b.setOnAction(e -> {
                int index = getTableView().getItems().size() -1;
                getTableView().getItems().add(index, createNewItem("button", index));
            });
            return b;
        }

        private MenuItem createMenuItem() {
            MenuItem item = new MenuItem("Add");
            item.setOnAction(e -> {
                if (isStandIn()) return;
                int index = getIndex();
                getTableView().getItems().add(index, createNewItem("menu", index));
            });
            return item;
        }


        private S createNewItem(String text, int index) {
            return (S) new Person(text + index, text + index, text);
        }

    }

    private Person createNewItem(String text, int index) {
        return new Person(text + index, text + index, text);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent()));
        primaryStage.setTitle(FXUtils.version());
        primaryStage.show();
    }

    /**
     * Marker-Interface to denote a class as not mutable.
     */
    public static interface StandIn {
    }

    public static class PersonStandIn extends Person implements StandIn{

         public PersonStandIn() {
            super("standIn", "", "");
        }

    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TablePersonAddRowAndEdit.class.getName());
}

Обновить

не должен был быть слишком удивлен - связанная с этим проблема обсуждалась полгода назад (и выдал отчет об ошибке)

Другие вопросы по тегам