Blabsl
Blabsl

Reputation: 125

JavaFX - RadioButtons in TableCell with Add Data Button

I'm currently working on an application where I use a TableView including RadioButtons in one TableCell. For that I created an own RadioButtonCell class. I also have a "Add new Row"-Button to let the user add some additional rows. After clicking the "Add" button a third time, I get more RadioButtonCells than rows. I don't find the mistake in my code.

Here a screenshot after clicking the Button a third time:

part of the tableview

RadioButtonCell:

import java.util.EnumSet;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;

public class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{

    private EnumSet<T> enumeration;

    public RadioButtonCell(EnumSet<T> enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    protected void updateItem(T item, boolean empty)
    {
        super.updateItem(item, empty);
        if (!empty) 
        {
            // GUI
            HBox hb = new HBox(7);
            hb.setAlignment(Pos.CENTER);
            final ToggleGroup group = new ToggleGroup();

            // create a radio button for each 'element' of the enumeration
            for (Enum<T> enumElement : enumeration) {
                RadioButton radioButton = new RadioButton(enumElement.toString());
                radioButton.setUserData(enumElement);
                radioButton.setToggleGroup(group);
                hb.getChildren().add(radioButton);
                if (enumElement.equals(item)) {
                    radioButton.setSelected(true);
                }
            }

            // issue events on change of the selected radio button
            group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {

                @SuppressWarnings("unchecked")
                @Override
                public void changed(ObservableValue<? extends Toggle> observable,
                        Toggle oldValue, Toggle newValue) {
                    getTableView().edit(getIndex(), getTableColumn());
                    RadioButtonCell.this.commitEdit((T) newValue.getUserData());
                }
            });
            setGraphic(hb);
        } 
    }
}

Model:

public class Points {

    private final SimpleObjectProperty<Participation> participation = new SimpleObjectProperty<Participation>(); // radio buttons


    public static enum Participation {
        NONE, HALO, BORDER;

        public String toString() {
            return super.toString().toLowerCase();
        };
    }

    /**
     * Constructor.
     * @param <Participation>
     */
    public Points(Participation p) {

        this.participation.setValue(p);

    public void setParticipation(Participation p){
        participation.set(p);
    }

    public Participation getParticipation(){
        return participation.get();
    }

    public SimpleObjectProperty<Participation> ParticipationProperty() {
        return participation;
    }
}

FXML:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<VBox prefHeight="900.0" prefWidth="1250.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="address.view.DataOverviewController">
   <children>
      <SplitPane fx:id="splitPaneVertical" prefHeight="776.0" prefWidth="1200.0" VBox.vgrow="ALWAYS">
         <items>
            <TabPane fx:id="tabPane" stylesheets="@Theme.css">
               <tabs>
                  <Tab text="Points">
                     <content>
                        <SplitPane fx:id="splitPaneHorizontal" dividerPositions="0.949874686716792" orientation="VERTICAL" stylesheets="@Theme.css">
                          <items>
                              <TableView fx:id="pointsDataTable">
                                <columns>                                
                                  <TableColumn fx:id="pointsBackgroundColumn" prefWidth="200.0" resizable="false" text="Background" />                                 
                                </columns>                                            
                              </TableView>
                            <AnchorPane SplitPane.resizableWithParent="false">
                                 <children>
                                    <HBox fx:id="pointsHBoxButton" alignment="CENTER" prefHeight="35.0" prefWidth="1016.0" spacing="20.0" stylesheets="@Theme.css" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                                       <children>
                                          <Button fx:id="pointsAddButton" mnemonicParsing="false" onAction="#handleAddPoints" text="Add" />                     
                                     </children>
                                </HBox>
                             </children>
                          </AnchorPane>
                       </items>
                    </SplitPane>
                 </content>
              </Tab>
           </tabs>
        </TabPane>
     </items>
  </SplitPane>

Controller:

    public class DataOverviewController {
        @FXML
        private TableView<Points> pointsDataTable;

        @FXML
        private TableColumn<Points, Participation> pointsBackgroundColumn;

        @FXML
        private Button pointsButtonAdd;

        public DataOverviewController() {
        }

        @FXML
        private void initialize() {
            // select multipe rows, make rows editable
            pointsDataTable.setEditable(true);
            pointsDataTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
            linesDataTable.setEditable(true);

             pointsBackgroundColumn.setCellFactory((param) -> new RadioButtonCell<Points, Participation>(EnumSet.allOf(Participation.class)));
             pointsBackgroundColumn.setCellValueFactory(new PropertyValueFactory<Points, Participation>("Participation"));
             pointsBackgroundColumn.setOnEditCommit(
                        new EventHandler<CellEditEvent<Points, Participation>>() {
                            @Override
                            public void handle(CellEditEvent<Points, Participation> t) {
                                ((Points) t.getTableView().getItems().get(
                                    t.getTablePosition().getRow())
                                    ).setParticipation(t.getNewValue());
                            }
                        }
                    );

        public void setMainConfig(MainConfig mainConfig) {
            // Add observable list data to the table
            pointsDataTable.setItems(mainConfig.getTableDataPoints());
        }

        @FXML
        private void handleAddPoints() {
            // create new record and add it to the tableview
            Points dataPoints = new Points(0, "Points", "TabFileName.tab",Participation.NONE, false, false, 18, "new");
            pointsDataTable.getItems().add(dataPoints);
        }
    }
}

I reduced my code to the important parts. Maybe someone can help? Thanks.

Upvotes: 1

Views: 1196

Answers (1)

fabian
fabian

Reputation: 82461

Items can be added as well as removed from TableCells. This means you need to handle the case where the TableCell becomes empty by undoing any changes done to the cell when a item was added. Otherwise there may be TableCells that look as if they contain a item although they are actually empty.

A updateItem method should look like this:

@Override
protected void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);

    if (empty) {
        // modifications to restore the empty state
    } else {
        // customized way of displaying the item
    }
}

Furthermore I do not recommend recreating the Nodes for displaying the item in the cell every time the item is changed, since this means you'll have a lot of unnecessary node creations which is the thing TableView is trying to avoid by reusing cells. Store them in fields and keep them for later use instead.

Upvotes: 1

Related Questions