I'm writing a simple JavaFX application which consists of 2 parts. - Left SplitPane is a ListView (a list of question) - When click on a question, the right SplitPane will be populated with a TableView (a list of answers which has 2 columns: type and name Depend on the question type, the type column will be checkboxes or radioboxes.
Example:
Question 1 - type checkbox -- [] Answer 1 -- [] Answer 2 -- [] Answer 3
Question 2 - type radio -- O Answer 4 -- O Answer 5
When I check answer 1 & 2 from question 1 then navigate to question 2 and left come back, only the Answer 2 is checked.
I've already debugged my code and there is no problem with my data list
My problem is when I tick more than 1 checkboxes from question 1 and then navigate to question 2 which has radioboxes then comeback, only the last checkbox will be checked.
I found out 1 problem is the ToogleGroup which used in the question 2. When I remove it, the problem is gone but now I can tick both Answer 4 & 5 which is wrong.
Here is my code:
public class SampleController implements Initializable {
@FXML
private ListView<Question> list;
@FXML
private TableView<Answer> tbDetails;
@FXML
private TableColumn tcAction;
@FXML
private TableColumn<Answer, String> tcName;
private List<Question> questions;
@Override
public void initialize(URL location, ResourceBundle resources) {
questions = new ArrayList<Question>();
}
public void afterInit() {
ObservableList<Question> data = FXCollections.observableArrayList();
// Question 1
Question question = new Question();
question.setIdx(0);
question.setName("Question 1");
question.setType(0);
List<Answer> answers = new ArrayList<Answer>();
answers.add(new Answer("Answer 1", false));
answers.add(new Answer("Answer 2", false));
answers.add(new Answer("Answer 3", false));
answers.add(new Answer("Answer 4", false));
answers.add(new Answer("Answer 5", false));
question.setAnswers(answers);
questions.add(question);
// Question 2
question = new Question();
question.setIdx(1);
question.setName("Question 2");
question.setType(1);
answers = new ArrayList<Answer>();
answers.add(new Answer("Answer 6", false));
answers.add(new Answer("Answer 7", false));
question.setAnswers(answers);
questions.add(question);
data.addAll(questions);
if (list != null) {
list.setItems(data);
list.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
list.setCellFactory(new Callback<ListView<Question>, ListCell<Question>>() {
@Override
public ListCell<Question> call(ListView<Question> param) {
final ListCell<Question> cell = new ListCell<Question>() {
@Override
public void updateItem(Question item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getName());
}
}
};
return cell;
}
});
list.getSelectionModel().selectedItemProperty().addListener(listItemSelected);
}
tcName.setCellValueFactory(new PropertyValueFactory<Answer, String>("name"));
tcAction.setCellValueFactory(new PropertyValueFactory("correct"));
}
/**
* Listen to changes in the list selection, and updates the table widget and
* DeleteIssue and NewIssue buttons accordingly.
*/
private final ChangeListener<Question> listItemSelected = new ChangeListener<Question>() {
@Override
public void changed(ObservableValue<? extends Question> observable, Question oldValue, Question newValue) {
questionUnselected(oldValue);
questionSelected(newValue);
}
};
/**
* Called when a question is unselected.
*
* @param oldQuestion Old question
*/
private void questionUnselected(Question oldQuestion) {
if (oldQuestion != null) {
tbDetails.getSelectionModel().clearSelection();
int questionListIdx = oldQuestion.getIdx();
Question question = questions.get(questionListIdx);
question.setAnswers(tbDetails.getItems());
}
}
/**
* Called when a question is selected.
*
* @param newQuestion New question
*/
private void questionSelected(Question newQuestion) {
if (newQuestion != null) {
final ToggleGroup radioGrp = new ToggleGroup();
tcAction.setCellFactory(new Callback<TableColumn, TableCell>() {
@Override
public TableCell call(TableColumn p) {
if (newQuestion.getType() == 1) {
return new RadioCell(radioGrp);
}
return new CheckboxCell();
}
});
ObservableList<Answer> data = FXCollections.observableArrayList();
int questionListIdx = newQuestion.getIdx();
Question question = questions.get(questionListIdx);
data.addAll(question.getAnswers());
tbDetails.setItems(data);
}
}
public class RadioCell extends TableCell<Answer, Boolean> {
private RadioButton radioBtn;
public RadioCell(ToggleGroup group) {
this.radioBtn = new RadioButton();
this.radioBtn.setAlignment(Pos.CENTER);
this.radioBtn.setToggleGroup(group);
setAlignment(Pos.CENTER);
setGraphic(radioBtn);
}
public RadioCell() {
this.radioBtn = new RadioButton();
this.radioBtn.setAlignment(Pos.CENTER);
//this.radioBtn.setToggleGroup(group);
setAlignment(Pos.CENTER);
setGraphic(radioBtn);
}
@Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
paintCell();
}
}
private void paintCell() {
if (radioBtn == null) {
radioBtn = new RadioButton();
}
radioBtn.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
setItem(newValue);
if (getTableRow().getIndex() >= 0) {
((Answer) getTableView().getItems().get(getTableRow().getIndex())).setCorrect(newValue);
}
}
});
radioBtn.setSelected(getValue());
setText(null);
setGraphic(radioBtn);
}
private Boolean getValue() {
return getItem() == null ? false : getItem();
}
public class CheckboxCell extends TableCell<Answer, Boolean> {
private CheckBox checkbox;
public CheckboxCell() {
this.checkbox = new CheckBox();
this.checkbox.setAlignment(Pos.CENTER);
setAlignment(Pos.CENTER);
setGraphic(checkbox);
}
@Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
paintCell();
}
}
private void paintCell() {
if (checkbox == null) {
checkbox = new CheckBox();
}
checkbox.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
setItem(newValue);
if (getTableRow().getIndex() >= 0) {
((Answer) getTableView().getItems().get(getTableRow().getIndex())).setCorrect(newValue);
}
}
});
checkbox.setSelected(getValue());
setText(null);
setGraphic(checkbox);
}
private Boolean getValue() {
return getItem() == null ? false : getItem();
}
Thanks in advance
Using your cell implementation with a RadioButton will not work as expected due to:
The cells are not 1:1 related to items in the table, they are only view-ports. In the following view:
The list of Answer items has 5 members, but the view only has 3 cells to view them. So your RadioButtons will NOT be 1:1 related to Answer items. Anyhow using any state, like the once allocated RadioButton in your cell will not work due to the view-port function of a cell, the view will assign different Answer items dynamicall to the same cell. So you might:
a) implement it in the model
I recommend to implement a toggle function in your model (Answer and Question), in a way that Answers instances will ask their parent Question instances to clear 'correct' properties of other Answers if they are of type RADIOBUTTON (1). (Question akt as ToggleGroup, Answers akt like Toggles)
b) implement it in the view
Add an onAction listener to the Checkbox and trigger a 'clear' of all other correct values with the same Question parent if of type (1)...