checkboxjavafxlistenerfxmljavafx-tableview

Working checkboxes in JavaFX table (CheckBoxTableCell)


(This is similar to a homework question.)

I recently made an example UI in Scenebuilder for something I later had to program with Java Swing. That more or less worked. Now it is my task, not for the actual development of the program, but for learning something in my job training, to make a similar UI with Scenebuilder, but this time an actually working one. The specifications are:

Making the check boxes act as radio boxes should be easy, if I could just enable the editing. I found a lot of examples that do almost what I want, but are still all not really applicable to my situation. Here are some of them:

Here is a picture of how the dialog looks now, I still can't use the check boxes: enter image description here

My question: How can I make it so that the check boxes react to clicks? React can mean outputting something on the console, I don't need a given code that makes it automatically disable the other checkbox, I want to figure that part out myself.

My code:

src.controller.MainController.java

package controller;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import view.Table;
public class MainController implements Initializable{
 @FXML TableView<Table> tableID;
 @FXML TableColumn<Table,String> iFirstName;
 @FXML TableColumn<Table,String> iLastName;
 @FXML TableColumn<Table,Boolean> iMalebox;
 @FXML TableColumn<Table,Boolean> iFemalebox;
 @Override public void initialize(URL location,ResourceBundle resources){
  iFirstName.setCellValueFactory(new PropertyValueFactory<Table,String>("rFirstName"));
  iLastName.setCellValueFactory(new PropertyValueFactory<Table,String>("rLastName"));
  iMalebox.setCellValueFactory(p->p.getValue().getCompleted());
  iMalebox.setCellFactory(p->new CheckBoxTableCell<>());
  iMalebox.setEditable(true);
  // iMalebox.setCellValueFactory(p->p.getValue().getCompleted());
  // iMalebox.setCellFactory(p->new CheckBoxTableCell<>());
  iFemalebox.setCellValueFactory(p->p.getValue().getCompleted());
  iFemalebox.setCellFactory(p->new CheckBoxTableCell<>());
  // iMalebox.setCellValueFactory(new PropertyValueFactory<Table,Boolean>("rMalebox"));
  // iFemalebox.setCellValueFactory(new PropertyValueFactory<Table,Boolean>("rFemalebox"));
  tableID.setItems(FXCollections.observableArrayList(new Table("Horst","Meier",true,false),new Table("Anna","Becker",false,true),new Table("Karl","Schmidt",true,false)));
  tableID.setEditable(true);
 }
}

src.controller.MainView.java

package controller;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class MainView extends Application{
 @Override public void start(Stage primaryStage){
  try{
   // FXMLLoader.load(MainView.class.getResource("MainController.fxml"));
   AnchorPane page=(AnchorPane)FXMLLoader.load(MainView.class.getResource("MainController.fxml"));
   Scene scene=new Scene(page);
   primaryStage.setScene(scene);
   primaryStage.setTitle("Window Title");
   primaryStage.show();
  }catch(Exception e){
   Logger.getLogger(MainView.class.getName()).log(Level.SEVERE,null,e);
  }
 }
 public static void main(String[] args){
  Application.launch(MainView.class,(java.lang.String[])null);
 }
}

src.controller.MainController.fxml

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
 <children>
<TableView fx:id="tableID" prefHeight="494.0" prefWidth="798.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="iFirstName" prefWidth="75.0" text="First name" />
<TableColumn fx:id="iLastName" prefWidth="75.0" text="Last name" />
            <TableColumn fx:id="iMalebox" prefWidth="75.0" text="Male" />
            <TableColumn fx:id="iFemalebox" prefWidth="75.0" text="Female" />
</columns>
</TableView>
 </children>
</AnchorPane>

src.view.Table.java

package view;
import javafx.beans.InvalidationListener;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class Table{
 private SimpleStringProperty rFirstName;
 private SimpleStringProperty rLastName;
 private SimpleBooleanProperty rMalebox;
 private SimpleBooleanProperty rFemalebox;
 public Table(String sFirstName,String sLastName,Boolean sMalebox,Boolean sFemalebox){
  rFirstName=new SimpleStringProperty(sFirstName);
  rLastName=new SimpleStringProperty(sLastName);
  rMalebox=new SimpleBooleanProperty(sMalebox);
  rMalebox.addListener((ChangeListener)(observable,oldValue,newValue)->{
   System.out.println("test");
   System.out.println("abc");
  });
  rFemalebox=new SimpleBooleanProperty(sFemalebox);
 }
 public String getRFirstName(){
  return rFirstName.get();
 }
 public void setRFirstName(String v){
  rFirstName.set(v);
 }
 public String getRLastName(){
  return rLastName.get();
 }
 public void setRLastName(String v){
  rLastName.set(v);
 }
 public Boolean getRMalebox(){
  return rMalebox.get();
 }
 public void setRMalebox(Boolean v){
  rMalebox.set(v);
 }
 public Boolean getRFemalebox(){
  return rFemalebox.get();
 }
 public void setRFemalebox(Boolean v){
  rFemalebox.set(v);
 }
 public ObservableValue<Boolean> getCompleted(){
  return new ObservableValue<Boolean>(){
   @Override public void removeListener(InvalidationListener arg0){}
   @Override public void addListener(InvalidationListener arg0){}
   @Override public void removeListener(ChangeListener<? super Boolean> listener){}
   @Override public Boolean getValue(){
    return null;
   }
   @Override public void addListener(ChangeListener<? super Boolean> listener){
    System.out.println("Test");
   }
  };
 }
}

Solution

  • I found a solution. Since I changed so much (I worked on getting this stupid thing to work for almost 20 hours after asking that question), it's not really useful to enumerate all the changes I did. But here is at least a working example.
    There are very few lines responsible for making the "male" and "female" boxes trigger each other's opposites (which is specific to my task), everything else is just for getting actually properly working CheckBoxTableCells. One would think that that is a really common case and there should be standard methods for that, like for "print text into the console" or "read file", but apparently not, apparently everything has to be complicated in programming with UIs.

    Anyway, rant aside, here is the working code:

    src.controller.MainView.java

    package controller;
    import java.io.*;
    import javafx.application.*;
    import javafx.fxml.*;
    import javafx.scene.*;
    import javafx.scene.layout.*;
    import javafx.stage.*;
    public class MainView extends Application{
     @Override public void start(Stage primaryStage) throws IOException{
      primaryStage.setScene(new Scene((AnchorPane)FXMLLoader.load(MainView.class.getResource("MainController.fxml"))));
      primaryStage.show();
     }
     public static void main(String[] args){
      Application.launch(MainView.class);
     }
    }
    

    src.controller.MainController.java

    package controller;
    import java.net.*;
    import java.util.*;
    import javafx.beans.value.*;
    import javafx.collections.*;
    import javafx.fxml.*;
    import javafx.scene.control.*;
    import javafx.scene.control.TableColumn.*;
    import javafx.scene.control.cell.*;
    import javafx.util.*;
    import view.*;
    public class MainController implements Initializable{
     @FXML TableView<Table> tableID;
     @FXML TableColumn<Table,String> iFirstName;
     @FXML TableColumn<Table,String> iLastName;
     @FXML TableColumn<Table,Boolean> iMalebox;
     @FXML TableColumn<Table,Boolean> iFemalebox;
     @Override public void initialize(URL location,ResourceBundle resources){
      iFirstName.setCellValueFactory(new PropertyValueFactory<Table,String>("rFirstName"));
      iLastName.setCellValueFactory(new PropertyValueFactory<Table,String>("rLastName"));
      iMalebox.setCellValueFactory(new Callback<CellDataFeatures<Table,Boolean>,ObservableValue<Boolean>>(){
       @Override public ObservableValue<Boolean> call(CellDataFeatures<Table,Boolean> cellData){
        return cellData.getValue().maleCheckedProperty(true);
       }
      });
      iMalebox.setCellFactory(new Callback<TableColumn<Table,Boolean>,TableCell<Table,Boolean>>(){
       @Override public TableCell<Table,Boolean> call(TableColumn<Table,Boolean> param){
        return new CheckBoxTableCell<>();
       }
      });
      iFemalebox.setCellValueFactory(new Callback<CellDataFeatures<Table,Boolean>,ObservableValue<Boolean>>(){
       @Override public ObservableValue<Boolean> call(CellDataFeatures<Table,Boolean> cellData){
        return cellData.getValue().femaleCheckedProperty(true);
       }
      });
      iFemalebox.setCellFactory(new Callback<TableColumn<Table,Boolean>,TableCell<Table,Boolean>>(){
       @Override public TableCell<Table,Boolean> call(TableColumn<Table,Boolean> param){
        return new CheckBoxTableCell<>();
       }
      });
      tableID.setItems(FXCollections.observableArrayList(new Table("Horst","Meier",true),new Table("Anna","Becker",false),new Table("Karl","Schmidt",true)));
     }
    }
    

    src.view.Table.java

    package view;
    import javafx.beans.property.*;
    public class Table{
     private String rFirstName;
     private String rLastName;
     public Table(String sFirstName,String sLastName,Boolean sMale){
      rFirstName=sFirstName;
      rLastName=sLastName;
      maleCheckedProperty(false).set(sMale);
     }
     private SimpleBooleanProperty maleChecked=new SimpleBooleanProperty(false);
     private SimpleBooleanProperty femaleChecked=new SimpleBooleanProperty(false);
     public SimpleBooleanProperty maleCheckedProperty(boolean recursion){
      if(recursion) femaleCheckedProperty(false).set(!maleChecked.get());
      return maleChecked;
     }
     public SimpleBooleanProperty femaleCheckedProperty(boolean recursion){
      if(recursion) maleCheckedProperty(false).set(!femaleChecked.get());
      return femaleChecked;
     }
     public String getRFirstName(){
      return rFirstName;
     }
     public String getRLastName(){
      return rLastName;
     }
    }
    

    src.controller.MainController.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    <?import javafx.scene.control.TableColumn?>
    <?import javafx.scene.control.TableView?>
    <?import javafx.scene.layout.AnchorPane?>
    <AnchorPane prefHeight="98.0" prefWidth="218.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
     <children>
     <TableView fx:id="tableID" editable="true" prefHeight="494.0" prefWidth="798.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
      <columns>
       <TableColumn fx:id="iFirstName" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="63.0" text="First name" />
       <TableColumn fx:id="iLastName" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="63.0" text="Last name" />
       <TableColumn fx:id="iMalebox" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="45.0" text="Male"/>
       <TableColumn fx:id="iFemalebox" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="45.0" text="Female"/>
       </columns>
      </TableView>
     </children>
    </AnchorPane>