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

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

Причина: java.lang.RuntimeException: TreeTableColumn.visible: Связанное значение не может быть установлено.

Если я использую двунаправленную привязку, я не получаю эту ошибку. Но мне не нужно двунаправленное связывание. Это ошибка или я неправильно использую bind? Пожалуйста, используйте приведенный ниже код, чтобы воспроизвести эту ошибку. Я использую 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>

0 ответов

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

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

В результате 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 имеет прослушиватель, который перестраивает меню и повторно связывает свойства, каждый раз, когда отображается столбец.

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

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

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

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

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

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