Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8235630

Checkbox Table Cell not working when value property is not writable

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • tbd
    • jfx11, jfx13, 8u192, 9
    • javafx
    • x86_64
    • linux

      ADDITIONAL SYSTEM INFORMATION :
      OS: Manjaro Linux, gnome edition.
      Java: Open JDK 13.0.1
      JavaFX: Open JFX 13.0.1
      IDE: IntelliJ Idea
      Pacakge management: Maven 3.6.2-1
      Java Preview features: disabled.
      CPU: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
      RAM: 4GB

      A DESCRIPTION OF THE PROBLEM :
      I need to show a read only value in a TableColumn<S, Boolean>, I set the cell factory to a CheckBoxTableCell, then the value factory is set to new PropertyValueFactory("readonlybooleanprop") and then all checkboxes are always unselected.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Create the bean class, with theese properties:
      private final BooleanProperty death = new SimpleBooleanProperty(true);
      private final ReadOnlyBooleanWrapper canUse = new ReadOnlyBooleanWrapper(false);
      2. Create the method for canUse in this way:
      public ReadOnlyBooleanProperty canUseProperty(){
          return canUse.getReadOnlyProperty();
      }
      3. Bind values in constructor:
      canUse.bind(death.not());
      4. Create a form and a controller class with a tableview and the following column:
      TableColumn<Bean, Boolean> readOnlyColumn;
      5. Set the cell factory and value factory as follows:
      readOnlyColumn.setCellValueFactory(new PropertyValueFactory<>("canUse"));
      readOnlyColumn.setCellFactory(CheckBoxTableCell.forTableColumn(readOnlyColumn));


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      When running the app, all the objects with value true in property death should show false in property canUse, and vice versa, but only column death is editable.
      ACTUAL -
      While column death works as expected, column canUse show only false values. When I use the default cell factory, it shows the boolean values as String and values are ok. So, the bug is with checkbox cells.

      ---------- BEGIN SOURCE ----------

      import javafx.application.Platform;
      import javafx.beans.property.*;
      import javafx.collections.FXCollections;
      import javafx.scene.Scene;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableView;
      import javafx.scene.control.cell.CheckBoxTableCell;
      import javafx.scene.control.cell.PropertyValueFactory;
      import javafx.stage.Stage;
      import javafx.stage.StageStyle;
      import org.junit.jupiter.api.Test;

      import java.util.Objects;
      import java.util.concurrent.CountDownLatch;

      /**
       * Test class to run with JUnit.
       */
      class MyTest {
          /**
           * CountDown wich allows to wait until UI test finishes.
           */
          private final CountDownLatch FLOW_CONTROL = new CountDownLatch(1);

          @Test
          void testCheckBoxColumn() throws InterruptedException {
              //Startup JavaFX and run buildandshow.
              Platform.startup(this::buildAndShowUI);
              //Await to stage closing.
              FLOW_CONTROL.await();
              //Free resources.
              Platform.exit();
          }

          @SuppressWarnings("unchecked")
          private void buildAndShowUI() {
              //Build the table view.
              var table = new TableView<>(FXCollections.observableArrayList(
                      new MyBean(1, true),
                      new MyBean(2, false),
                      new MyBean(3, true),
                      new MyBean(4, true),
                      new MyBean(5, false),
                      new MyBean(6, false),
                      new MyBean(7, true),
                      new MyBean(8, true)
              ));

              //Build an integer column
              var idColumn = new TableColumn<MyBean, Integer>("ID");
              idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));
              idColumn.setEditable(false);

              //Build a CheckBox column for the SimpleProperty.
              var deathColumn = new TableColumn<MyBean, Boolean>("Is Death");
              deathColumn.setCellValueFactory(new PropertyValueFactory<>("death"));
              deathColumn.setCellFactory(CheckBoxTableCell.forTableColumn(deathColumn));
              deathColumn.setEditable(true);

              //Build a checkbox column for the readonly property bound to death.not()
              var canUseColumn = new TableColumn<MyBean, Boolean>("Can Use");
              canUseColumn.setCellValueFactory(new PropertyValueFactory<>("canUse"));
              canUseColumn.setCellFactory(CheckBoxTableCell.forTableColumn(canUseColumn));
              canUseColumn.setEditable(false);

              //Build a text column to see the value at canUseColumn
              var canUsetext = new TableColumn<MyBean, Boolean>("Can Use?");
              canUsetext.setCellValueFactory(new PropertyValueFactory<>("canUse"));
              canUsetext.setEditable(false);

              //Add columns
              table.getColumns().addAll(idColumn, deathColumn, canUseColumn, canUsetext);
              table.setEditable(true);

              //Create the scene
              var scene = new Scene(table);

              //Create the window and show
              var stage = new Stage(StageStyle.DECORATED);
              stage.setOnHidden(e -> FLOW_CONTROL.countDown());
              stage.setScene(scene);
              stage.show();
              /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              !Looking at the window, canUseText shows the text for the boolean value !
              !read from the canUse property. And you can see that it doesn't match with !
              !the state of check boxes at the checkBox column. !
              !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
               */
          }

          /**
           * An hypotetical bean class.
           */
          @SuppressWarnings({"WeakerAccess", "unused"})
          public static class MyBean {
              /**
               * Autogenerated numeric ID.
               */
              private final IntegerProperty id = new SimpleIntegerProperty();
              /**
               * If this object is death.
               */
              private final BooleanProperty death = new SimpleBooleanProperty();
              /**
               * Flag to know if you can use this object. Of course, it's the opossite of death.
               */
              private final ReadOnlyBooleanWrapper canUse = new ReadOnlyBooleanWrapper();

              /**
               * Constructs a new bean object.
               *
               * @param id autogenerated id.
               * @param death value of death property.
               */
              public MyBean(int id, boolean death) {
                  canUse.bind(this.death.not());
                  setId(id);
                  setDeath(death);
              }

              public final IntegerProperty idProperty() {
                  return id;
              }

              public final int getId() {
                  return id.get();
              }

              public final void setId(int value) {
                  id.set(value);
              }

              public final BooleanProperty deathProperty() {
                  return death;
              }

              public final boolean isDeath() {
                  return death.get();
              }

              public final void setDeath(boolean value) {
                  death.set(value);
              }

              public final ReadOnlyBooleanProperty canUseProperty() {
                  return canUse.getReadOnlyProperty();
              }

              public final boolean getCanUse() {
                  return canUse.get();
              }

              @Override
              public boolean equals(Object o) {
                  if (this == o) return true;
                  if (o == null || getClass() != o.getClass()) return false;
                  var myBean = (MyBean) o;
                  return Objects.equals(getId(), myBean.getId());
              }

              @Override
              public int hashCode() {
                  return Objects.hash(getId());
              }
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The method for canUse stated in step 2 to reproduce, will return the readOnly wrapper instead of the readOnlyProperty and everything works fine.
      public ReadOnlyBooleanWrapper canUseProperty(){
          return canUse;
      }
      But this workaround is breaking the purpose of a readonly property.

      FREQUENCY : always


            aghaisas Ajit Ghaisas
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: