TreeTableColumn.visible : связанное значение не может быть установлено

Я делаю простое приложение JavaFX. В этом приложении есть древовидная таблица с 2 столбцами и флажком. Если флажок установлен, столбец 2 будет виден, иначе не будет виден. Для этого я привязал видимое свойство столбца таблицы дерева к выбранному свойству флажка. Когда я нажимаю на флажок, состояние столбца изменяется, но в то же время дает.

Вызвано: java.lang.RuntimeException: TreeTableColumn.visible: невозможно установить связанное значение.

Если я использую двунаправленную привязку, я не получаю эту ошибку. Но мне не нужна двунаправленная привязка. Это ошибка или я неправильно использую привязку? Пожалуйста, используйте приведенный ниже код, чтобы воспроизвести эту ошибку. Я использую jdk1.8.0_111.

JavaFXApplication.java

package test;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class JavaFXApplication extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

FXMLDocumentController.java

package test;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;

public class FXMLDocumentController implements Initializable {

    @FXML
    private TreeTableView<?> table;
    @FXML
    private CheckBox box;
    @FXML
    private TreeTableColumn<?, ?> c2;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        c2.visibleProperty().bind(box.selectedProperty());
    }    

}

FXMLDocument.fxml

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

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TreeTableColumn?>
<?import javafx.scene.control.TreeTableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="390.0" prefWidth="452.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="javafxapplication2.FXMLDocumentController">
    <children>
      <TreeTableView fx:id="table" layoutX="2.0" prefHeight="390.0" prefWidth="149.0">
        <columns>
          <TreeTableColumn prefWidth="75.0" text="C1" />
          <TreeTableColumn fx:id="c2" prefWidth="75.0" text="C2" visible="false" />
        </columns>
      </TreeTableView>
      <CheckBox fx:id="box" layoutX="234.0" layoutY="77.0" mnemonicParsing="false" text="CheckBox" />
    </children>
</AnchorPane>

person sakit    schedule 16.11.2016    source источник
comment
Что произойдет, если вы удалите атрибут visible="false" из FXML? Я бы предположил, что свойства FXML устанавливаются перед вызовом метода initialize, но, возможно, все наоборот...   -  person Itai    schedule 16.11.2016
comment
@sillyfly Я тоже так думаю и удаляю из файла fxml, но результат тот же   -  person sakit    schedule 16.11.2016
comment
TableView и TreeTableView поддерживают раскрывающееся меню, в котором можно выбрать видимые столбцы. Вероятно, поддержка этого, даже если вы его не используете, вызывает вызов setVisible в столбцах где-то (и, следовательно, ошибка). Я бы просто заменил привязку прослушивателем в состоянии выбора флажка, хотя привязка немного более элегантна.   -  person James_D    schedule 16.11.2016
comment
@James_D да, вы пишете, я получил эту ошибку из-за того, что свойство visible столбца уже неявно связано с кнопкой меню, даже если вы не используете эту кнопку.   -  person sakit    schedule 16.11.2016
comment
так что мы не можем использовать метод привязки?   -  person sakit    schedule 16.11.2016
comment
@sakit Похоже, нет. Просто используйте слушателя вместо этого.   -  person James_D    schedule 16.11.2016
comment
в порядке. Спасибо @James_D   -  person sakit    schedule 16.11.2016
comment
Для этого уже существует отчет об ошибке? Я думаю, что такого поведения не должно быть. Привязки должны использоваться, а не выдавать ошибки там, где их быть не должно.   -  person geisterfurz007    schedule 20.07.2017
comment
@geisterfurz007 я не знаю   -  person sakit    schedule 20.07.2017
comment
У меня возникла та же проблема, но только с Java 8, и я не смог воспроизвести проблему с Java 11. Кажется, для этой проблемы есть отчет об ошибке, который был исправлен в Java 9. bugs.openjdk.java.net/browse/JDK-8136468.   -  person DJViking    schedule 14.11.2019


Ответы (1)


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

TableHeaderRow отвечает за отображение всех заголовков столбцов и включает в себя эту встроенную кнопку для меню, которое по умолчанию представляет собой список выбора столбцов для отображения/скрытия:

TableView с кнопкой заголовка столбца

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

На самом деле эту привязку можно отменить, но меня это раздражает, поскольку TableHeaderRow равно null, пока не отобразится TableView. Но, адаптировав это решение для доступа к TableHeaderRow, мы можем сделать что-то вроде:

      TableHeaderRow headerRow = ((TableViewSkinBase) tableView.getSkin()).getTableHeaderRow();
      try {

        // get columnPopupMenu field
        Field privateContextMenuField = TableHeaderRow.class.getDeclaredField("columnPopupMenu");

        // make field public
        privateContextMenuField.setAccessible(true);

        // get context menu
        ContextMenu contextMenu = (ContextMenu) privateContextMenuField.get(headerRow);

        for (MenuItem menuItem : contextMenu.getItems()) {
          // Assuming these will be CheckMenuItems in the default implementation
          BooleanProperty selectedProperty = ((CheckMenuItem) menuItem).selectedProperty();

          // In theory these menu items are in parallel with the columns, but I just brute forced it to test
          for (TableColumn<?, ?> tableColumn : tableView.getColumns()) {
            // Unlink the column's visibility with the menu item
            tableColumn.visibleProperty().unbindBidirectional(selectedProperty);
          }
        }

      } catch (Exception ex) {
        ex.printStackTrace();
      }

      // Not strictly necessary but we don't want to be misleading :-p
      tableView.setTableMenuButtonVisible(false);

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

Можно было бы, конечно, найти способ отключить этот слушатель... но очевидно, что это уже нелепо и, вероятно, не нужно.

Во всяком случае, как вы заметили:

Если я использую двунаправленную привязку, я не получаю эту ошибку. Но мне не нужна двунаправленная привязка.

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

Итак, я бы использовал двунаправленную привязку.

person hinerm    schedule 28.06.2019