Контекстное меню в TableRow<Object> не отображается при первом щелчке правой кнопкой мыши

Поэтому я последовал этому примеру при использовании контекстного меню с TableViews из здесь. Я заметил, что с помощью этого кода

row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
.otherwise((ContextMenu)null));

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

if (row.getItem() != null) {
    rowMenu.show(row, event.getScreenX(), event.getScreenY());
}
else {
    // do nothing
}

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

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

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

Вот MCVE:

public class MCVE_TableView extends Application{

@Override
public void start(Stage primaryStage) throws Exception {
    BorderPane myBorderPane = new BorderPane();
    TableView<People> myTable = new TableView<>();

    TableColumn<People, String> nameColumn = new TableColumn<>();
    TableColumn<People, Integer> ageColumn = new TableColumn<>();

    ContextMenu rowMenu = new ContextMenu();

    ObservableList<People> peopleList = FXCollections.observableArrayList();
    peopleList.add(new People("John Doe", 23));

    nameColumn.setMinWidth(100);
    nameColumn.setCellValueFactory(
        new PropertyValueFactory<>("Name"));

    ageColumn.setMinWidth(100);
    ageColumn.setCellValueFactory(
        new PropertyValueFactory<>("Age"));

    myTable.setItems(peopleList);
    myTable.getColumns().addAll(nameColumn, ageColumn);

    myTable.setRowFactory(tv -> {
        TableRow<People> row = new TableRow<>();

        row.setOnContextMenuRequested((event) -> {
            People selectedRow = row.getItem();
            rowMenu.getItems().clear();

            MenuItem sampleMenuItem = new MenuItem("Sample Button");
            if (selectedRow != null) {
                if (selectedRow.getAge() > 100) {
                    sampleMenuItem.setDisable(true);
                }

                rowMenu.getItems().add(sampleMenuItem);
            }
            else {
                event.consume();
            }

            /*if (row.getItem() != null) { // this block comment displays the context menu instantly
                rowMenu.show(row, event.getScreenX(), event.getScreenY());
            }
            else {
                // do nothing
            }*/

            // this requires the row to be right clicked 2 times before displaying the context menu
            row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
            .then(rowMenu)
            .otherwise((ContextMenu)null));
        });

        return row;
    });

    myBorderPane.setCenter(myTable);

    Scene scene = new Scene(myBorderPane, 500, 500);
    primaryStage.setTitle("MCVE");
    primaryStage.setScene(scene);
    primaryStage.show();
}

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

} Вот класс людей

public class People {
SimpleStringProperty name;
SimpleIntegerProperty age;
public People(String name, int age) {
    this.name = new SimpleStringProperty(name);
    this.age = new SimpleIntegerProperty(age);
}

public SimpleStringProperty NameProperty() {
    return this.name;
}
public SimpleIntegerProperty AgeProperty() {
    return this.age;
}
public String getName() {
    return this.name.get();
}
public int getAge() {
    return this.age.get();
}

}

Обновлено: добавлен MCVE

Edit2: обновлен MCVE. По-прежнему требуется дважды щелкнуть правой кнопкой мыши, прежде чем появится всплывающее меню contextMenu.

Привет! Я только что добавил MCVE. Спасибо

Fatnam 10.04.2019 13:53

спасибо :) NPE возникает из-за того, что не проверяется, является ли selectedItem нулевым (перед доступом к его getAge).. понятия не имею, почему он не отображается при первом щелчке в непустой строке, может быть ошибка

kleopatra 10.04.2019 14:06

возможно, это не ошибка: вы, кажется, заново создаете contextMenu при каждом запросе - делайте это один раз во время создания экземпляра и привязывайте этот единственный (для каждой строки) contextMenu к его contextMenuProperty, если он не пуст.

kleopatra 10.04.2019 14:11

После создания contextMenu во время создания экземпляра, как я могу использовать это и связать одиночное (для каждой строки) contextMenu? if (row.contextMenuProperty() != null) { row.getContextMenu().addAll(edit, merge, unmergeSchedule, delete); }. Я не знаю, как я могу использовать contextMenu, созданный во время создания экземпляра.

Fatnam 10.04.2019 14:49

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

Fatnam 10.04.2019 15:11

Я обновил MCVE. NPE теперь отсутствует, так как я последовал одному из ваших предложений (проверьте, имеет ли selectedItem значение null). contextMenu по-прежнему появляется после двойного щелчка правой кнопкой мыши, даже если contextMenu создается во время создания экземпляра.

Fatnam 10.04.2019 15:32
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ниже приведен фрагмент кода в качестве быстрой демонстрации того, как и где создавать и настраивать контекстное меню для каждой строки. Это

  • создает ContextMenu/MenuItem для каждого TableRow во время создания экземпляра строки
  • создает условную привязку, которая привязывает меню к contextMenuProperty строки, если оно не пустое (точно так же, как и вы)
  • настраивает contextMenu в обработчике onShowing в зависимости от текущего элемента (примечание: нет необходимости в защите от null, поскольку условная привязка неявно гарантирует, что в этом случае меню не будет отображаться)

Фрагмент:

myTable.setRowFactory(tv -> {
    TableRow<People> row = new TableRow<>() {
      ContextMenu rowMenu = new ContextMenu();
      MenuItem sampleMenuItem = new MenuItem("Sample Button");
      {
          rowMenu.getItems().addAll(sampleMenuItem);
          contextMenuProperty()
              .bind(Bindings
                  .when(Bindings.isNotNull(itemProperty()))
                  .then(rowMenu).otherwise((ContextMenu) null));
          rowMenu.setOnShowing(e -> {
              People selectedRow = getItem();
              sampleMenuItem.setDisable(selectedRow.getAge() > 100);
          });
      }

    };
    return row;
}); 

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