Javafx 8 Tableview selection with checkbox - javafx

I've set up a multiselection enabled tableview and am trying to attach a listener a checkbox inserted into a column to the selection model of the table.
checkBoxTableColumn.setCellValueFactory(
cellData -> {
CheckBox checkBox = new CheckBox();
ObjectProperty<CheckBox> sop = new SimpleObjectProperty<CheckBox>();
sop.setValue(checkBox);
sop.getValue().setText("");
sop.getValue().selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(getJobTableView().getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
ObservableValue<CheckBox> ov = sop;
return ov;
}
But that doesn't change the table selection.
Edited as jurge stated
checkBoxTableColumn.setCellFactory(new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxCell(jobTableView);
}
});
and checkbox cell is as
class CheckBoxCell extends TableCell<Job, Boolean>{
private CheckBox checkBox;
private TableView<Job> jobTableView;
public CheckBoxCell(TableView<Job> tableView){
this.jobTableView = tableView;
}
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
checkBox = new CheckBox();
setGraphic(checkBox);
checkBox.selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(jobTableView.getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
}
}
The listener wouldn't work this time.....
edit-#2
in initialize method of controller:
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
the job class:
private BooleanProperty isContextSelected;
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
edit--
Ignore the unnecessary parts. The whole code as requested.:
The controller:
package BillControl.view;
import BillControl.Controller.PopulateView;
import BillControl.Controller.Validator;
import BillControl.MainApp;
import BillControl.model.Article;
import BillControl.model.Job;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.TreeSet;
public class ArticleJobAssignmentController {
private Article article;
private Stage jobAssignStage;
private boolean okClicked = false;
private MainApp mainApp;
ArrayList<Job> selectedJobList = new ArrayList<>();
private ObservableList<Job> masterJobList = FXCollections.observableArrayList();
private ObservableList<Job> currentJobList = FXCollections.observableArrayList();
private ObservableList<Job> articleEngagementList = FXCollections.observableArrayList();
private TreeSet rowIndices = new TreeSet();
#FXML
private Label articleNameLabel;
#FXML
private Label noOfJobsLabel;
#FXML
private Button okButton;
#FXML
private Button cancelButton;
#FXML
private Label errorLabel;
#FXML
private TableView<Job> jobTableView;
#FXML
private TableColumn<Job, Boolean> checkBoxTableColumn;
#FXML
private TableColumn<Job, String> jobNameColumn;
#FXML
private TableColumn<Job, String> clientNameColumn;
#FXML
private TableColumn<Job, Integer> noOfArticlesColumn;
#FXML
private TableColumn<Job, String> alreadyEngagedColumn;
public ArticleJobAssignmentController(){
}
public void initialize(){
errorLabel.setVisible(false);
jobTableView.setEditable(true);
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
jobNameColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty()
);
noOfArticlesColumn.setCellValueFactory(
cellData -> {
SimpleIntegerProperty sip = new SimpleIntegerProperty(cellData.getValue().numberOfArticlesProperty().getValue());
ObservableValue<Integer> ov = sip.asObject();
return ov;
}
);
alreadyEngagedColumn.setCellValueFactory(
cellData -> {
Boolean engaged = false;
for(Job job: articleEngagementList){
if(job.getNumberID().equals(cellData.getValue().getNumberID())){
engaged = true;
}
}
if(engaged){
SimpleStringProperty sbp = new SimpleStringProperty("Yes");
ObservableValue<String> ov = sbp;
return ov;
}
else {
SimpleStringProperty sbp = new SimpleStringProperty("No");
ObservableValue<String> ov = sbp;
return ov;
}
}
);
jobTableView.getSelectionModel().getSelectedItems().addListener(
new ListChangeListener<Job>() {
#Override
public void onChanged(Change<? extends Job> c) {
noOfJobsLabel.setText(String.valueOf(c.getList().size()));
}
}
);
}
public void filterMasterList(){
for(Job job : masterJobList){
if(!job.getIsCompleted()){
currentJobList.add(job);
}
}
for(Job currentJob : currentJobList){
currentJob.setIsContextSelected(false);
}
}
#FXML
public void handleOkClicked(){
if(!Validator.articleJobAssignment(this)){
for(Job job : jobTableView.getSelectionModel().getSelectedItems()){
selectedJobList.add(job);
}
okClicked = true;
jobAssignStage.close();
}
else {
errorLabel.setText("Select at least one job");
errorLabel.setVisible(true);
}
}
#FXML
public void handleCancelClicked(){
jobAssignStage.close();
}
public void setArticle(Article article) {
this.article = article;
articleNameLabel.setText(article.getName());
}
public void setJobAssignStage(Stage jobAssignStage) {
this.jobAssignStage = jobAssignStage;
}
public void setOkClicked(boolean okClicked) {
this.okClicked = okClicked;
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
setMasterJobList(mainApp.getJobObservableList());
filterMasterList();
jobTableView.setItems(currentJobList);
if(article != null){
articleEngagementList = PopulateView.articleCurrentEngagementList(articleEngagementList, article.getId(), mainApp.getClientObservableList());
}
}
public Label getArticleNameLabel() {
return articleNameLabel;
}
public Label getNoOfJobsLabel() {
return noOfJobsLabel;
}
public Button getOkButton() {
return okButton;
}
public Button getCancelButton() {
return cancelButton;
}
public TableView<Job> getJobTableView() {
return jobTableView;
}
public TableColumn<Job, String> getJobNameColumn() {
return jobNameColumn;
}
public TableColumn<Job, String> getClientNameColumn() {
return clientNameColumn;
}
public TableColumn<Job, Integer> getNoOfArticlesColumn() {
return noOfArticlesColumn;
}
public ObservableList<Job> getMasterJobList() {
return masterJobList;
}
public void setMasterJobList(ObservableList<Job> masterJobList) {
this.masterJobList = masterJobList;
}
public boolean isOkClicked() {
return okClicked;
}
public ArrayList<Job> getSelectedJobList() {
return selectedJobList;
}
}
the job class(ignore the constructors):
package BillControl.model;
import BillControl.Controller.PopulateItems;
import BillControl.GeneralUtils.DateUtil;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Job {
private String numberID;
private StringProperty name;
private ObjectProperty<Client> client;
private StringProperty clientName;
private ObjectProperty<Date> startPeriod;
private ObjectProperty<Date> endPeriod;
private ObjectProperty<Date> startDate;
private ObjectProperty<Date> targetDate;
private BooleanProperty isDelayed;
private LongProperty remainingDays;
private LongProperty delayedDays;
private BooleanProperty isCompleted;
private ObjectProperty<Date> completionDate;
private LongProperty daysToComplete;
private StringProperty partner;
private IntegerProperty numberOfArticles;
private BooleanProperty isContextSelected;
private String clientID;
public Job(Client client){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>(client);
this.clientName = new SimpleStringProperty(client.getName());
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
// todo check fill others logic
}
public Job(ObservableList clientList, String numberID){
this.numberID = numberID;
// this.numberID = null;
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty();
this.numberOfArticles = new SimpleIntegerProperty();
this.isContextSelected = new SimpleBooleanProperty(false);
PopulateItems.populateJob(this);
Client selectedClient = null;
for(Object clientObject : clientList){
Client queriedClient = (Client) clientObject;
String name = queriedClient.getName();
String queriedName = PopulateItems.clientID2NameHelper(this.getClientID());
if(name.equals(queriedName)){
selectedClient = (Client) clientObject;
break;
}
}
this.setClient(selectedClient);
this.setClientName(this.getClient().getName());
this.fillOthers(true);
}
public Job(){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty("");
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
}
public void fillOthers(Boolean filledJob){
if(filledJob){
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
try {
Date startDate = this.getStartDate();
Date completionDate = this.getCompletionDate();
Date currentDate = DateUtil.getCurrentDate();
Date targetDate = this.getTargetDate();
if (this.getIsCompleted()){
// completion days
this.daysToComplete = new SimpleLongProperty();
long duration = completionDate.getTime() - startDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setDaysToComplete(diffInDays);
}
else{
this.remainingDays = new SimpleLongProperty();
this.isDelayed = new SimpleBooleanProperty();
if (targetDate.after(currentDate) && !this.getIsCompleted()){
// remaining days
long duration = targetDate.getTime() - currentDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(diffInDays);
this.setIsDelayed(false);
}
else if (targetDate.before(currentDate) && !this.getIsCompleted()) {
// delayed days
this.delayedDays = new SimpleLongProperty();
this.setIsDelayed(true);
long duration = currentDate.getTime() - targetDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(0);
this.setDelayedDays(diffInDays);
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
else {
//TODO client creation form job
}
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public Client getClient() {
return client.get();
}
public ObjectProperty<Client> clientProperty() {
return client;
}
public void setClient(Client client) {
this.client.set(client);
}
public Date getStartDate() {
return startDate.get();
}
public ObjectProperty<Date> startDateProperty() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate.set(startDate);
}
public Date getTargetDate() {
return targetDate.get();
}
public ObjectProperty<Date> targetDateProperty() {
return targetDate;
}
public void setTargetDate(Date targetDate) {
this.targetDate.set(targetDate);
}
public boolean getIsDelayed() {
return isDelayed.get();
}
public BooleanProperty isDelayedProperty() {
return isDelayed;
}
public void setIsDelayed(boolean isDelayed) {
this.isDelayed.set(isDelayed);
}
public long getRemainingDays() {
return remainingDays.get();
}
public LongProperty remainingDaysProperty() {
return remainingDays;
}
public void setRemainingDays(long remainingDays) {
this.remainingDays.set(remainingDays);
}
public long getDelayedDays() {
return delayedDays.get();
}
public LongProperty delayedDaysProperty() {
return delayedDays;
}
public void setDelayedDays(long delayedDays) {
this.delayedDays.set(delayedDays);
}
public boolean getIsCompleted() {
return isCompleted.get();
}
public BooleanProperty isCompletedProperty() {
return isCompleted;
}
public void setIsCompleted(boolean isCompleted) {
this.isCompleted.set(isCompleted);
}
public Date getCompletionDate() {
return completionDate.get();
}
public ObjectProperty<Date> completionDateProperty() {
return completionDate;
}
public void setCompletionDate(Date completionDate) {
this.completionDate.set(completionDate);
}
public long getDaysToComplete() {
return daysToComplete.get();
}
public LongProperty daysToCompleteProperty() {
return daysToComplete;
}
public void setDaysToComplete(long daysToComplete) {
this.daysToComplete.set(daysToComplete);
}
public String getPartner() {
return partner.get();
}
public StringProperty partnerProperty() {
return partner;
}
public void setPartner(String partner) {
this.partner.set(partner);
}
public Integer getNumberOfArticles() {
return numberOfArticles.get();
}
public IntegerProperty numberOfArticlesProperty() {
return numberOfArticles;
}
public void setNumberOfArticles(int numberOfArticles) {
this.numberOfArticles.set(numberOfArticles);
}
public String getNumberID() {
return numberID;
}
public String getClientName() {
return clientName.get();
}
public StringProperty clientNameProperty() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName.set(clientName);
}
public Date getStartPeriod() {
return startPeriod.get();
}
public ObjectProperty<Date> startPeriodProperty() {
return startPeriod;
}
public void setStartPeriod(Date startPeriod) {
this.startPeriod.set(startPeriod);
}
public Date getEndPeriod() {
return endPeriod.get();
}
public ObjectProperty<Date> endPeriodProperty() {
return endPeriod;
}
public void setEndPeriod(Date endPeriod) {
this.endPeriod.set(endPeriod);
}
public String getClientID() {
return clientID;
}
public void setClientID(String clientID) {
this.clientID = clientID;
}
public boolean getIsContextSelected() {
return isContextSelected.get();
}
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
public void setIsContextSelected(boolean isContextSelected) {
this.isContextSelected.set(isContextSelected);
}
}

Ok, so this one way to do it. You'll need a BooleanProperty in your backing model to hold the value of the check box so that the table will 'remember' if that rows check box should be selected or not if the row scrolls out of view and then back again.
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( new Callback<TableColumn<Job,Boolean>, TableCell<Job,Boolean>>()
{
#Override
public TableCell<Job,Boolean> call( TableColumn<Job,Boolean> param )
{
return new CheckBoxTableCell<Job,Boolean>()
{
{
setAlignment( Pos.CENTER );
}
#Override
public void updateItem( Boolean item, boolean empty )
{
if ( ! empty )
{
TableRow row = getTableRow();
if ( row != null )
{
int rowNo = row.getIndex();
TableViewSelectionModel sm = getTableView().getSelectionModel();
if ( item ) sm.select( rowNo );
else sm.clearSelection( rowNo );
}
}
super.updateItem( item, empty );
}
};
}
} );
checkCol.setEditable( true );
checkCol.setMaxWidth( 50 );
checkCol.setMinWidth( 50 );

You are using checkBoxTableColumn.setCellValueFactory incorrectly.
Your TableView has data items of type T, and the setCellValueFactory method on a column is there to tell the column what value it must extract out of an object of type T to display.
You however are returning an observable value containing a GUI component (CheckBox), whereas you should be returning an observable Boolean value extracted from cellData.
See here for an Oracle tutorial on TableView: http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJAGAAEE
Adding a checkbox column to a table where changes to the table checkbox are propogated back to the model object is quite simple:
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( CheckBoxTableCell.forTableColumn( checkCol ) );
Note that "checkBoxValue" is the partial name of a property method in Job called checkBoxValueProperty() that returns a BooleanProperty. (It doesn't have to be called checkBoxValue, you can give it a different name, but it must end with Property.)

Related

Multiple Controls needed in JavaFX Tree Table Cell

I'm needing different editable controls in a JavaFX TreeTableCell based on an enum attribute value of the row object.
In different cases I need a DatePicker, a TextField, a CheckBox, a ComboBox, or a simple non editable Text field.
I've extended TreeTableCell and overridden the updateItem to handle different cases but that is getting very cumbersome.
Is it possible to create a custom CellFactory Callback to return different subclassed TreeTableCells based on attributes of the row object? How might I go about doing this?
public class MyCellFactory implements Callback<TreeTableColumn<MyField,String>,TreeTableCell<MyField,String>> {
#Override
public TreeTableCell<MyField, String> call(TreeTableColumn<MyField, String> param) {
return new MyCell();
}
}
public class MyCell extends TreeTableCell<MyField, String> {
private TextField textField;
private DatePicker datePicker;
private CheckBox checkBox;
private Text text;
private ComboBox<String> comboBox;
public MyCell() {
super();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || getTreeTableRow() == null) {
setText(null);
setGraphic(null);
} else {
MyField myField = (MyField) getTreeTableRow().getItem();
if (isEditing()) {
if (myField.getFieldType().equals(MyFieldType.CheckBox)) {
if (checkBox != null) {
checkBox.setSelected(getBoolean());
}
setText(null);
setGraphic(checkBox);
} else if (myField.getFieldType().equals(MyFieldType.Date)) {
if (datePicker != null) {
datePicker.setValue(getDate());
}
setText(null);
setGraphic(datePicker);
} else {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
}
//...
}
//...
}
I've implemented an SSCCE version of James_D's method but am having trouble understanding how to commit and update changes to the different cells. I'll post the corrected version Once I find a solution
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SampleApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#SuppressWarnings("unchecked")
#Override
public void start(Stage primaryStage) throws Exception {
TreeItem<MyField> fooFields = new TreeItem<MyField>(new MyField("Foo", "Foo", null, false, null));
TreeItem<MyField> fooText = new TreeItem<MyField>(new MyField("fooText", "fooText", "text", true, null));
TreeItem<MyField> fooCheck = new TreeItem<MyField>(new MyField("fooCheck", "fooCheck", "check", true, null));
List<String> fooCombos = Arrays.asList("foo Combo 1", "foo Combo 2");
TreeItem<MyField> fooCombo = new TreeItem<MyField>(
new MyField("fooCombo", "foo Combo", "combo", true, fooCombos));
fooFields.getChildren().addAll(fooText, fooCheck, fooCombo);
TreeItem<MyField> barFields = new TreeItem<MyField>(new MyField("Bar", "Bar", null, false, null));
TreeItem<MyField> barText = new TreeItem<MyField>(new MyField("barText", "barText", "text", true, null));
TreeItem<MyField> barCheck = new TreeItem<MyField>(new MyField("barCheck", "barCheck", "check", true, null));
List<String> barCombos = Arrays.asList("bar Combo 1", "bar Combo 2");
TreeItem<MyField> barCombo = new TreeItem<MyField>(
new MyField("barCombo", "bar Combo", "combo", true, barCombos));
barFields.getChildren().addAll(barText, barCheck, barCombo);
TreeItem<MyField> hiddenRoot = new TreeItem<MyField>(new MyField("hidden", "hidden", null, false, null));
hiddenRoot.getChildren().addAll(fooFields, barFields);
TreeTableView<MyField> treeTable = new TreeTableView<>(hiddenRoot);
treeTable.setEditable(true);
treeTable.setPrefWidth(400);
treeTable.setShowRoot(false);
TreeTableColumn<MyField, String> nameCol = new TreeTableColumn<MyField, String>("Name");
nameCol.setPrefWidth(150);
nameCol.setCellValueFactory(new TreeItemPropertyValueFactory<MyField, String>("name"));
TreeTableColumn<MyField, String> valueCol = new TreeTableColumn<MyField, String>("Value");
valueCol.setPrefWidth(250);
valueCol.setCellValueFactory(new TreeItemPropertyValueFactory<MyField, String>("value"));
valueCol.setCellFactory(new MyFieldCellFactory());
treeTable.getColumns().addAll(nameCol, valueCol);
HBox root = new HBox(treeTable);
root.setStyle("-fx-padding: 10;" + "-fx-border-style: solid inside;" + "-fx-border-width: 2;"
+ "-fx-border-insets: 5;" + "-fx-border-radius: 5;" + "-fx-border-color: blue;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Multi Control Tree Table View");
primaryStage.show();
}
public class MyField {
private String name;
private String value;
public String fieldType;
public boolean isEditable;
public List<String> comboVals;
public MyField(String name, String value, String fieldType, boolean isEditable, List<String> comboVals) {
super();
this.name = name;
this.value = value;
this.fieldType = fieldType;
this.isEditable = isEditable;
this.comboVals = comboVals;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getFieldType() {
return fieldType;
}
public void setFieldType(String fieldType) {
this.fieldType = fieldType;
}
public List<String> getComboVals() {
return comboVals;
}
public void setComboVals(List<String> comboVals) {
this.comboVals = comboVals;
}
public boolean isEditable() {
return isEditable;
}
public void setEditable(boolean isEditable) {
this.isEditable = isEditable;
}
}
public class MyFieldCellFactory
implements Callback<TreeTableColumn<MyField, String>, TreeTableCell<MyField, String>> {
#Override
public TreeTableCell<MyField, String> call(TreeTableColumn<MyField, String> param) {
return new MyFieldCell();
}
}
public class MyFieldCell extends TreeTableCell<MyField, String> {
private MyEditingControlProvider controlProvider = new MyCellEditingControlProvider();
public MyFieldCell() {
super();
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
MyField myField = getTreeTableRow().getItem();
setText(null);
setGraphic(controlProvider.getControl(myField));
}
}
protected void commitEdit() {
super.commitEdit(getItem());
MyField myField = getTreeTableRow().getItem();
controlProvider.updateFromControl(myField);
}
}
public interface MyEditingControlProvider {
public Control getControl(MyField field);
public void updateFromControl(MyField field);
}
public class MyCellEditingControlProvider implements MyEditingControlProvider {
private Map<String, MyEditingControlProvider> providers;
public MyCellEditingControlProvider() {
providers = new HashMap<>();
providers.put("check", new CheckProvider());
providers.put("combo", new ComboProvider());
providers.put("text", new TextProvider());
}
#Override
public Control getControl(MyField field) {
if (field == null || field.getFieldType() == null) {
return null;
} else {
return providers.get(field.getFieldType()).getControl(field);
}
}
#Override
public void updateFromControl(MyField field) {
providers.get(field.getFieldType()).updateFromControl(field);
}
}
public class CheckProvider implements MyEditingControlProvider {
private CheckBox checkBox;
#Override
public Control getControl(MyField field) {
if (checkBox == null) {
createCheckBox(field);
}
return checkBox;
}
private void createCheckBox(MyField field) {
checkBox = new CheckBox("Check");
checkBox.setSelected(getBoolean(field));
}
private Boolean getBoolean(MyField field) {
return field.getValue() == null ? false : convertYNToBoolean(field.getValue());
}
private Boolean convertYNToBoolean(String val) {
if (val != null && val.equals("Y")) {
return true;
} else {
return false;
}
}
private String convertBooleanToYN(Boolean val) {
if (val) {
return "Y";
} else {
return "N";
}
}
#Override
public void updateFromControl(MyField field) {
field.setValue(convertBooleanToYN(checkBox.isSelected()));
}
}
public class ComboProvider implements MyEditingControlProvider {
private ComboBox<String> comboBox;
#Override
public Control getControl(MyField field) {
if (comboBox == null) {
createComboBox(field);
}
return comboBox;
}
private void createComboBox(MyField field) {
comboBox = new ComboBox<String>();
comboBox.setEditable(true);
resetBox(field);
}
private void resetBox(MyField field) {
comboBox.getItems().clear();
comboBox.getItems().addAll(field.getComboVals());
}
#Override
public void updateFromControl(MyField field) {
field.setValue(comboBox.getValue());
}
}
public class TextProvider implements MyEditingControlProvider {
private TextField textField;
#Override
public Control getControl(MyField field) {
if (textField == null) {
createTextField(field);
}
return textField;
}
private void createTextField(MyField field) {
textField = new TextField(field.getValue());
}
#Override
public void updateFromControl(MyField field) {
field.setValue(textField.getText());
}
}
}
The cell that is returned by the cell factory will be reused by the TreeTableView as the user expands and collapses items in the tree, or scrolls through the data, etc. So the cell you return must be able to handle all cases, and you cannot return a cell instance that only handles specific rows.
If you want to refactor, you have to refactor the code in the updateItem(...) method, which you can do to any degree of modularity you want. A (perhaps extreme) example might be:
public interface EditingControlProvider {
public Control getControl(MyField myField);
}
with some specific implementations:
public class DatePickerProvider implements EditingControlProvider {
private DatePicker datePicker ;
#Override
public Control getControl(MyField myField) {
if (datePicker == null) {
datePicker = new DatePicker();
}
datePicker.setValue(myField.getDate());
return datePicker ;
}
}
and similarly for other controls.
Then you can do
public class CellEditingControlProvider implements EditingControlProvider {
private Map<MyFieldType, EditingControlProvider> providers ;
public CellEditingControlProvider() {
providers = new HashMap<>();
providers.put(MyFieldType.CheckBox, new CheckBoxProvider());
providers.put(MyFieldType.Date, new DatePickerProvider());
// etc...
}
#Override
public Control getControl(MyField myField) {
return providers.get(myField.getFieldType()).getControl(myField);
}
}
And now your actual cell implementation reduces to:
public class MyCell extends TreeTableCell<MyField, String> {
private EditingControlProvider controlProvider = new CellEditingControlProvider();
public MyCell() {
super();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || getTreeTableRow() == null) {
setText(null);
setGraphic(null);
} else {
MyField myField = (MyField) getTreeTableRow().getItem();
if (isEditing()) {
setText(null);
setGraphic(controlProvider.getControl(myField));
}
//...
}
//...
}
}
If you need to implement the commitEdit(...) method in the cell, you can add a method to the interface, e.g.
public void updateFromControl(MyField myField) ;
with (I think) the obvious implementations throughout, e.g.
public class DatePickerProvider implements EditingControlProvider {
// existing code...
#Override
public void updateFromControl(MyField myField) {
myField.setDate(datePicker.getValue());
}
}

SceneBuilder shows Layout failure ("null") when loading FXML with custom control

I'm tryin to get a custom control working in the SceneBuilder.
What I've build is a control called ContentSection. To play around with the possibility of creating custom controls I nealry copied the code of a titled-pane and made some changes to the skin to match my future requirements of a ContentSection.
My control is working fine in my application, but I can not load it in the SceneBuilder.
Here is my code of the control:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.Orientation;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.util.Duration;
#DefaultProperty("content")
public class ContentSection extends Control {
public ContentSection() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
// initialize pseudo-class state
pseudoClassStateChanged(PSEUDO_CLASS_EXPANDED, true);
pseudoClassStateChanged(PSEUDO_CLASS_COLLAPSED, false);
}
public ContentSection(final String title, final Node content) {
this();
setTitle(title);
setContent(content);
}
private final StringProperty titleProperty = new StringPropertyBase() {
#Override
protected void invalidated() {
notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "title";
};
};
public final void setTitle(final String value) {
titleProperty().set(value);
}
public final String getTitle() {
return titleProperty == null ? null : titleProperty.get();
}
public final StringProperty titleProperty() {
return titleProperty;
}
private DoubleProperty iconSizeProperty;
public final void setIconSize(final double value) {
iconSizeProperty().set(value);
}
public final double getIconSize() {
return iconSizeProperty == null ? -1 : iconSizeProperty.get();
}
public final DoubleProperty iconSizeProperty() {
if (iconSizeProperty == null) {
iconSizeProperty = new SimpleDoubleProperty(this, "iconSize", -1);
}
return iconSizeProperty;
}
private ObjectProperty<FontAwesomeIcon> iconProperty;
public final void setIcon(final FontAwesomeIcon value) {
iconProperty().set(value);
}
public final FontAwesomeIcon getIcon() {
return iconProperty == null ? null : iconProperty.get();
}
#Override
public String getUserAgentStylesheet() {
return ContentSection.class.getClassLoader().getResource("content-section.css").toExternalForm();
}
public final ObjectProperty<FontAwesomeIcon> iconProperty() {
if (iconProperty == null) {
iconProperty = new SimpleObjectProperty<>(this, "icon");
}
return iconProperty;
}
private ObjectProperty<Node> contentProperty;
public final void setContent(final Node value) {
contentProperty().set(value);
}
public final Node getContent() {
return contentProperty == null ? null : contentProperty.get();
}
public final ObjectProperty<Node> contentProperty() {
if (contentProperty == null) {
contentProperty = new SimpleObjectProperty<Node>(this, "content");
}
return contentProperty;
}
private final BooleanProperty expandedProperty = new BooleanPropertyBase(true) {
#Override
protected void invalidated() {
final boolean active = get();
pseudoClassStateChanged(PSEUDO_CLASS_EXPANDED, active);
pseudoClassStateChanged(PSEUDO_CLASS_COLLAPSED, !active);
notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "expanded";
}
};
public final void setExpanded(final boolean value) {
expandedProperty().set(value);
}
public final boolean isExpanded() {
return expandedProperty.get();
}
public final BooleanProperty expandedProperty() {
return expandedProperty;
}
private final BooleanProperty animatedProperty = new StyleableBooleanProperty(true) {
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "animated";
}
#Override
public CssMetaData<ContentSection, Boolean> getCssMetaData() {
return StyleableProperties.ANIMATED;
}
};
public final void setAnimated(final boolean value) {
animatedProperty().set(value);
}
public final boolean isAnimated() {
return animatedProperty.get();
}
public final BooleanProperty animatedProperty() {
return animatedProperty;
}
private final ObjectProperty<Duration> animationDurationProperty = new SimpleObjectProperty<>(this, "animationDuration", Duration.millis(350.0));
public final void setAnimationDuration(final Duration value) {
animationDurationProperty().set(value);
}
public final Duration getAnimationDuration() {
return animationDurationProperty().get();
}
public final ObjectProperty<Duration> animationDurationProperty() {
return animationDurationProperty;
}
private final BooleanProperty collapsibleProperty = new StyleableBooleanProperty(true) {
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "collapsible";
}
#Override
public CssMetaData<ContentSection, Boolean> getCssMetaData() {
return StyleableProperties.COLLAPSIBLE;
}
};
public final void setCollapsible(final boolean value) {
collapsibleProperty().set(value);
}
public final boolean isCollapsible() {
return collapsibleProperty.get();
}
public final BooleanProperty collapsibleProperty() {
return collapsibleProperty;
}
private final ObjectProperty<Number> headerSizeProperty = new StyleableObjectProperty<Number>(44.0) {
#Override
public String getName() {
return "headerSize";
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public CssMetaData<? extends Styleable, Number> getCssMetaData() {
return StyleableProperties.HEADER_SIZE;
}
};
public final void setHeaderSize(final Number value) {
headerSizeProperty().set(value);
}
public final Number getHeaderSize() {
return headerSizeProperty().get();
}
public final ObjectProperty<Number> headerSizeProperty() {
return headerSizeProperty;
}
/** {#inheritDoc} */
#Override
protected Skin<?> createDefaultSkin() {
return new ContentSectionSkin(this);
}
private static final String DEFAULT_STYLE_CLASS = "content-section";
private static final PseudoClass PSEUDO_CLASS_EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass PSEUDO_CLASS_COLLAPSED = PseudoClass.getPseudoClass("collapsed");
private static class StyleableProperties {
private static final CssMetaData<ContentSection, Boolean> COLLAPSIBLE =
new CssMetaData<ContentSection, Boolean>("-fx-collapsible", StyleConverter.getBooleanConverter(), Boolean.TRUE) {
#Override
public boolean isSettable(final ContentSection n) {
return n.collapsibleProperty == null || !n.collapsibleProperty.isBound();
}
#Override
public StyleableProperty<Boolean> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Boolean>) n.collapsibleProperty();
}
};
private static final CssMetaData<ContentSection, Number> HEADER_SIZE =
new CssMetaData<ContentSection, Number>("-fx-header-size", StyleConverter.getSizeConverter(), 44.0) {
#Override
public boolean isSettable(final ContentSection n) {
return n.collapsibleProperty == null || !n.collapsibleProperty.isBound();
}
#Override
public StyleableProperty<Number> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Number>) n.headerSizeProperty();
}
};
private static final CssMetaData<ContentSection, Boolean> ANIMATED =
new CssMetaData<ContentSection, Boolean>("-fx-animated", StyleConverter.getBooleanConverter(), Boolean.TRUE) {
#Override
public boolean isSettable(final ContentSection n) {
return n.animatedProperty == null || !n.animatedProperty.isBound();
}
#Override
public StyleableProperty<Boolean> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Boolean>) n.animatedProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
styleables.add(COLLAPSIBLE);
styleables.add(ANIMATED);
styleables.add(HEADER_SIZE);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
#Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
#Override
public Orientation getContentBias() {
final Node c = getContent();
return c == null ? super.getContentBias() : c.getContentBias();
}
#Override
public Object queryAccessibleAttribute(final AccessibleAttribute attribute, final Object... parameters) {
switch (attribute) {
case TEXT: {
final String accText = getAccessibleText();
if (accText != null && !accText.isEmpty()) {
return accText;
}
return getTitle();
}
case EXPANDED:
return isExpanded();
default:
return super.queryAccessibleAttribute(attribute, parameters);
}
}
#Override
public void executeAccessibleAction(final AccessibleAction action, final Object... parameters) {
switch (action) {
case EXPAND:
setExpanded(true);
break;
case COLLAPSE:
setExpanded(false);
break;
default:
super.executeAccessibleAction(action);
}
}
}
Here is my code of the skin:
import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView;
import javafx.animation.Animation.Status;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
#SuppressWarnings("restriction")
public class ContentSectionSkin extends BehaviorSkinBase<ContentSection, ContentSectionBehavior> {
private final BorderPane container;
private final HBox header;
private final BorderPane contentContainer;
private Timeline timeline;
private double transitionStartValue;
private DoubleProperty transition;
private FontAwesomeIconView expandCollapseIcon;
private Button expandCollapseButton;
private HBox sectionToolBar;
public ContentSectionSkin(final ContentSection contentSection) {
super(contentSection, new ContentSectionBehavior(contentSection));
transitionStartValue = 0;
container = createContainer();
getChildren().setAll(container);
header = createHeader();
container.setTop(header);
contentContainer = createContentContainer();
container.setCenter(contentContainer);
registerChangeListener(contentSection.contentProperty(), "CONTENT");
registerChangeListener(contentSection.expandedProperty(), "EXPANDED");
registerChangeListener(contentSection.collapsibleProperty(), "COLLAPSIBLE");
sectionToolBar.getChildren().addListener((ListChangeListener<Node>) c -> updateHeader());
if (contentSection.isExpanded()) {
setTransition(1.0f);
setExpanded(contentSection.isExpanded());
} else {
setTransition(0.0f);
if (getSkinnable().getContent() != null) {
getSkinnable().getContent().setVisible(false);
}
}
}
#Override
protected double computeMaxWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
return Double.MAX_VALUE;
}
#Override
protected double computeMinHeight(final double width, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerHeight = snapSize(header.prefHeight(width));
final double contentHeight = contentContainer.minHeight(width) * getTransition();
final double minHeight = headerHeight + snapSize(contentHeight) + topInset + bottomInset;
return minHeight;
}
#Override
protected double computeMinWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerWidth = snapSize(header.prefWidth(height));
final double contentWidth = snapSize(contentContainer.minWidth(height));
final double minWidth = Math.max(headerWidth, contentWidth) + leftInset + rightInset;
return minWidth;
}
#Override
protected double computePrefHeight(final double width, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerHeight = snapSize(header.prefHeight(width));
final double contentHeight = contentContainer.prefHeight(width) * getTransition();
final double prefHeight = headerHeight + snapSize(contentHeight) + topInset + bottomInset;
return prefHeight;
}
#Override
protected double computePrefWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerWidth = snapSize(header.prefWidth(height));
final double contentWidth = snapSize(contentContainer.prefWidth(height));
final double prefWidth = Math.max(headerWidth, contentWidth) + leftInset + rightInset;
return prefWidth;
}
private BorderPane createContainer() {
final BorderPane container = new BorderPane();
container.setMinSize(0.0, 0.0);
return container;
}
private BorderPane createContentContainer() {
final BorderPane contentContainer = new BorderPane();
contentContainer.getStyleClass().setAll("content");
contentContainer.setMinSize(0.0, 0.0);
if (getSkinnable().getContent() != null) {
contentContainer.setCenter(getSkinnable().getContent());
}
return contentContainer;
}
private Button createExpandCollapseButton() {
final Button expandCollapseButton = new Button();
expandCollapseButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
expandCollapseButton.setOnAction(event -> {
getSkinnable().setExpanded(!getSkinnable().isExpanded());
});
final StackPane expandCollapseIconContainer = new StackPane();
expandCollapseIconContainer.getStyleClass().setAll("icon-container");
expandCollapseIconContainer.setId("last");
expandCollapseIconContainer.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
expandCollapseIconContainer.setPrefSize(21, 21);
expandCollapseIconContainer.setMinSize(21, 21);
expandCollapseButton.setGraphic(expandCollapseIconContainer);
expandCollapseIcon = new FontAwesomeIconView(FontAwesomeIcon.CHEVRON_DOWN);
expandCollapseIcon.setStyleClass("icon");
expandCollapseIconContainer.getChildren().add(expandCollapseIcon);
return expandCollapseButton;
}
private HBox createHeader() {
final HBox header = new HBox();
header.getStyleClass().setAll("header");
header.setPrefHeight(getSkinnable().getHeaderSize().doubleValue());
header.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
header.getChildren().add(createIconContainer());
header.getChildren().add(createTitleLabel());
header.getChildren().add(createPlaceholder());
header.getChildren().add(createSectionToolBar());
return header;
}
private FontAwesomeIconView createIcon() {
final FontAwesomeIconView iconView = new FontAwesomeIconView();
iconView.setStyleClass("icon");
iconView.setIcon(getSkinnable().getIcon());
iconView.glyphSizeProperty().bind(getSkinnable().iconSizeProperty());
return iconView;
}
private StackPane createIconContainer() {
final StackPane iconContainer = new StackPane();
iconContainer.getStyleClass().setAll("icon-container");
iconContainer.setPrefSize(50, 50);
iconContainer.getChildren().add(createIcon());
return iconContainer;
}
private Pane createPlaceholder() {
final Pane placeholder = new Pane();
HBox.setHgrow(placeholder, Priority.ALWAYS);
return placeholder;
}
private HBox createSectionToolBar() {
sectionToolBar = new HBox();
sectionToolBar.getStyleClass().setAll("section-tool-bar");
expandCollapseButton = createExpandCollapseButton();
if (getSkinnable().isCollapsible()) {
sectionToolBar.getChildren().add(sectionToolBar.getChildren().size(), expandCollapseButton);
}
return sectionToolBar;
}
private Label createTitleLabel() {
final Label titleLabel = new Label();
titleLabel.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
titleLabel.textProperty().bind(getSkinnable().titleProperty());
return titleLabel;
}
private void disableCache() {
getSkinnable().getContent().setCache(false);
}
private void doAnimationTransition() {
if (getSkinnable().getContent() == null) {
return;
}
Duration duration;
if (timeline != null && (timeline.getStatus() != Status.STOPPED)) {
duration = timeline.getCurrentTime();
timeline.stop();
} else {
duration = getSkinnable().getAnimationDuration();
}
timeline = new Timeline();
timeline.setCycleCount(1);
KeyFrame k1, k2;
if (getSkinnable().isExpanded()) {
k1 = new KeyFrame(Duration.ZERO, event -> {
// start expand
getSkinnable().getContent().setVisible(true);
}, new KeyValue(transitionProperty(), transitionStartValue));
k2 = new KeyFrame(duration, event -> {
// end expand
}, new KeyValue(transitionProperty(), 1, Interpolator.LINEAR)
);
} else {
k1 = new KeyFrame(Duration.ZERO, event -> {
// Start collapse
}, new KeyValue(transitionProperty(), transitionStartValue));
k2 = new KeyFrame(duration, event -> {
// end collapse
getSkinnable().getContent().setVisible(false);
}, new KeyValue(transitionProperty(), 0, Interpolator.LINEAR));
}
timeline.getKeyFrames().setAll(k1, k2);
timeline.play();
}
private void enableCache() {
getSkinnable().getContent().setCache(true);
}
public BorderPane getContentContainer() {
return contentContainer;
}
private double getTransition() {
return transition == null ? 0.0 : transition.get();
}
#Override
protected void handleControlPropertyChanged(final String property) {
super.handleControlPropertyChanged(property);
if ("CONTENT".equals(property)) {
final Node content = getSkinnable().getContent();
if (content == null) {
contentContainer.setCenter(null);
} else {
contentContainer.setCenter(content);
}
} else if ("EXPANDED".equals(property)) {
setExpanded(getSkinnable().isExpanded());
} else if ("COLLAPSIBLE".equals(property)) {
updateHeader();
}
}
private void setExpanded(final boolean expanded) {
if (!getSkinnable().isCollapsible()) {
setTransition(1.0f);
return;
}
// we need to perform the transition between expanded / hidden
if (getSkinnable().isAnimated()) {
transitionStartValue = getTransition();
doAnimationTransition();
} else {
if (expanded) {
setTransition(1.0f);
} else {
setTransition(0.0f);
}
if (getSkinnable().getContent() != null) {
getSkinnable().getContent().setVisible(expanded);
}
getSkinnable().requestLayout();
}
}
private void setTransition(final double value) {
transitionProperty().set(value);
}
private DoubleProperty transitionProperty() {
if (transition == null) {
transition = new SimpleDoubleProperty(this, "transition", 0.0) {
#Override
protected void invalidated() {
container.requestLayout();
updateExpandCollapseIconRotation();
}
};
}
return transition;
}
private void updateExpandCollapseIconRotation() {
expandCollapseIcon.setRotate(180 * getTransition());
}
private void updateHeader() {
if (getSkinnable().isCollapsible() && !sectionToolBar.getChildren().contains(expandCollapseButton)) {
sectionToolBar.getChildren().add(sectionToolBar.getChildren().size(), expandCollapseButton);
} else if (sectionToolBar.getChildren().contains(expandCollapseButton)) {
sectionToolBar.getChildren().remove(expandCollapseButton);
}
}
}
And last but not least the behavior:
import static javafx.scene.input.KeyCode.SPACE;
import java.util.ArrayList;
import java.util.List;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import com.sun.javafx.scene.control.behavior.KeyBinding;
import javafx.scene.input.MouseEvent;
#SuppressWarnings("restriction")
public class ContentSectionBehavior extends BehaviorBase<ContentSection> {
private final ContentSection contentSection;
public ContentSectionBehavior(final ContentSection contentSection) {
super(contentSection, CONTENT_SECTION_BINDINGS);
this.contentSection = contentSection;
}
/***************************************************************************
* *
* Key event handling *
* *
**************************************************************************/
private static final String PRESS_ACTION = "Press";
protected static final List<KeyBinding> CONTENT_SECTION_BINDINGS = new ArrayList<KeyBinding>();
static {
CONTENT_SECTION_BINDINGS.add(new KeyBinding(SPACE, PRESS_ACTION));
}
#Override
protected void callAction(final String name) {
switch (name) {
case PRESS_ACTION:
if (contentSection.isCollapsible() && contentSection.isFocused()) {
contentSection.setExpanded(!contentSection.isExpanded());
contentSection.requestFocus();
}
break;
default:
super.callAction(name);
}
}
/***************************************************************************
* *
* Mouse event handling *
* *
**************************************************************************/
#Override
public void mousePressed(final MouseEvent e) {
super.mousePressed(e);
final ContentSection contentSection = getControl();
contentSection.requestFocus();
}
/**************************************************************************
* State and Functions *
*************************************************************************/
public void expand() {
contentSection.setExpanded(true);
}
public void collapse() {
contentSection.setExpanded(false);
}
public void toggle() {
contentSection.setExpanded(!contentSection.isExpanded());
}
}
To test my experiment I just created a simple fxml file:
<?xml version="1.0" encoding="UTF-8"?>
<?import org.test.ContentSection?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.StackPane?>
<ScrollPane fx:id="root" fitToWidth="true" fitToHeight="true" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<content>
<VBox styleClass="content">
<children>
<ContentSection fx:id="contentSection" collapsible="false" VBox.vgrow="ALWAYS">
<content>
<StackPane>
<children>
<Label text="Test" />
</children>
</StackPane>
</content>
</ContentSection>
</children>
</VBox>
</content>
</ScrollPane>
Now while opening this FXML file with the SceneBuilder the only thing happening is I'm getting a warning in top area of the SceneBuilder that says "Layout failure ('null')".
Does someone know what to do next?
I don't see an option to search for the problem...
Best regards
Patrick
The same error has just happened to me, and the problem is the css styles. I had to remove the styles and then add them to the FXML.

Issue with removing\adding items from JavaFX TableColumn's combo box CellFactory

I have a JavaFX TableColumn. The column has a ComboBoxTableCell populated by ObservableList I pass to it.
I have an "active" list and a "deactive" list for the combobox. After a selection is made, I wish to remove the selected item from the active, "real", list (and add it to the deactivated items list).
After a selection has been made, a CellEditEvent is being fired and sets up the row object by the selected one (from the combobox).
The thing is, when I remove the select item from the list, in the event handler, my CellEditEvent event handler got fired again - this time with a wrong "new value"!
Of course this behavior breaks my flow logic completely.
Any ideas about how to solve this situation? Thank you
An SSCCE of the situation:
package tableviewexample;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class TableViewExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<MappingItem> table = new TableView<>();
// FIRST COLUMN
TableColumn<MappingItem, String> colA = new TableColumn<>("Excel Column");
colA.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<MappingItem, String>, ObservableValue<String>> () {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<MappingItem, String> param) {
return new ReadOnlyObjectWrapper(param.getValue().getExcelColumnName());
}
});
//SECOND COLUMN
TableColumn<MappingItem, GoldplusField> colB = new TableColumn<>("Database Field Column");
colB.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<MappingItem, GoldplusField>, ObservableValue<GoldplusField>> () {
#Override
public ObservableValue<GoldplusField> call(TableColumn.CellDataFeatures<MappingItem, GoldplusField> param) {
return new ReadOnlyObjectWrapper(param.getValue().getGpField());
}
});
GoldplusField gp1 = new GoldplusField("T1", "fName", "First Name");
GoldplusField gp2 = new GoldplusField("T1", "phn", "Phone");
GoldplusField gp3 = new GoldplusField("T2", "lName", "Last Name");
GoldplusField gp4 = new GoldplusField("T2", "adrs", "Address");
ObservableList<GoldplusField> deactiveFieldsList = FXCollections.observableArrayList();
ObservableList<GoldplusField> activeFieldsList = FXCollections.observableArrayList(gp1, gp2, gp3, gp4);
colB.setCellFactory(ComboBoxTableCell.forTableColumn(new FieldToStringConvertor(), activeFieldsList));
colB.setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<MappingItem, GoldplusField>>() {
#Override
public void handle(TableColumn.CellEditEvent<MappingItem, GoldplusField> t) {
if (t.getNewValue() != null) {
deactiveFieldsList.add(t.getNewValue());
((MappingItem) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setGpField(t.getNewValue());
// ******************************************************************************************** //
// This creates a new instance of the EventHandler in which I get the "next" item on the List.
// ******************************************************************************************** //
activeFieldsList.remove(t.getNewValue());
}
}
}
);
//THIRD COLUMN
TableColumn<MappingItem, String> colC = new TableColumn<>("Test Column");
PropertyValueFactory<MappingItem, String> nameFac = new PropertyValueFactory<>("name");
colC.setCellValueFactory(nameFac);
colC.setCellFactory(TextFieldTableCell.forTableColumn());
table.setEditable(true);
table.getColumns().addAll(colA, colB, colC);
GoldplusField gp5 = new GoldplusField("T1", "other", "Other");
MappingItem mi1 = new MappingItem("name", gp5);
mi1.excelColumnName.set("name1");
MappingItem mi2 = new MappingItem("phone", gp5);
mi2.excelColumnName.set("nam2");
ObservableList<MappingItem> miList = FXCollections.observableArrayList(mi1, mi2);
table.setItems(miList);
StackPane root = new StackPane();
root.getChildren().add(table);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
class FieldToStringConvertor extends StringConverter<GoldplusField> {
#Override
public String toString(GoldplusField object) {
if (object != null)
return object.getGpName();
else
return "";
}
#Override
public GoldplusField fromString(String string) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
public class MappingItem {
private StringProperty excelColumnName = new SimpleStringProperty(this, "excelColumnName");
private ObjectProperty<GoldplusField> gpField = new SimpleObjectProperty<GoldplusField>(this, "gpField");
public String getExcelColumnName() { return excelColumnName.get(); }
public void setExcelColumnName(String excelColumnName) { this.excelColumnName.set(excelColumnName); }
public StringProperty excelColumnNameProperty() { return excelColumnName; }
public GoldplusField getGpField() { return gpField.get(); }
public void setGpField(GoldplusField gpField) { this.gpField.set(gpField); }
public ObjectProperty gpFieldProperty() { return this.gpField; }
public MappingItem(String columnName) { this.excelColumnName.set(columnName); }
public MappingItem(GoldplusField gpField) { this.gpField.set(gpField); }
public MappingItem(String columnName, GoldplusField gpField) {
this.excelColumnName.set(columnName);
this.gpField.set(gpField);
}
}
public class GoldplusField {
private StringProperty table = new SimpleStringProperty(this, "table");
private StringProperty dbName = new SimpleStringProperty(this, "dbName");
private StringProperty gpName = new SimpleStringProperty(this, "gpName");
public String getDbName() { return dbName.get(); }
public String getGpName() { return gpName.get(); }
public String getTable() { return table.get(); }
public void setDbName(String dbName) { this.dbName.set(dbName); }
public void setGpName(String gpName) { this.gpName.set(gpName); }
public void setTable(String table) { this.table.set(table); }
public StringProperty tableProperty() { return this.table; }
public StringProperty gpNameProperty() { return this.gpName; }
public StringProperty dbNameProperty() { return this.dbName; }
public GoldplusField(String table, String dbName, String gpName) {
this.dbName.set(dbName);
this.gpName.set(gpName);
this.table.set(table);
}
}
}

mirror one observableList to another

In JavaFX, I have an ObservableList of objects, and want another ObservableList that will mirror the first list but contain a String representation of each object. Is there anything simpler than writing a custom ListChangeListener to do the conversion ? I have a StringConverter which can provide the mirrored value.
Similarly, given an ObservableList<String>, how do I create a second ObservableList<String> that has a constant entry at index 0, and mirrors the first list beginning at index 1?
For the first question, the easiest way to do this is to use the EasyBind framework. Then it is as simple as
ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);
Here is an SSCCE using EasyBind:
import org.fxmisc.easybind.EasyBind;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
public class MappedAndTransformedListExample {
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
#Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
#Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
namesList.forEach(System.out::println);
namesList.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown#example.com"));
namesList.forEach(System.out::println);
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
}
If you prefer not to use a third-party framework for some reason, you can use a TransformationList (which is what EasyBind does under the hood: I copied the code below from the source code there and modified it).
In the above, you would replace
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
with
ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {
#Override
public int getSourceIndex(int index) {
return index ;
}
#Override
public String get(int index) {
return converter.toString(getSource().get(index));
}
#Override
public int size() {
return getSource().size();
}
#Override
protected void sourceChanged(Change<? extends Person> c) {
fireChange(new Change<String>(this) {
#Override
public boolean wasAdded() {
return c.wasAdded();
}
#Override
public boolean wasRemoved() {
return c.wasRemoved();
}
#Override
public boolean wasReplaced() {
return c.wasReplaced();
}
#Override
public boolean wasUpdated() {
return c.wasUpdated();
}
#Override
public boolean wasPermutated() {
return c.wasPermutated();
}
#Override
public int getPermutation(int i) {
return c.getPermutation(i);
}
#Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
#Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(Person removedPerson: c.getRemoved()) {
res.add(converter.toString(removedPerson));
}
return res;
}
#Override
public int getFrom() {
return c.getFrom();
}
#Override
public int getTo() {
return c.getTo();
}
#Override
public boolean next() {
return c.next();
}
#Override
public void reset() {
c.reset();
}
});
}
};
For the second question, you must use a transformation list. Here's an updated main(...) method that shows how to do this. (It works just as well with the second version of part 1.)
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
#Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
#Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {
#Override
public int getSourceIndex(int index) {
return index - 1 ;
}
#Override
public String get(int index) {
if (index == 0) {
return "Contacts";
} else {
return getSource().get(index - 1);
}
}
#Override
public int size() {
return getSource().size() + 1 ;
}
#Override
protected void sourceChanged(Change<? extends String> c) {
fireChange(new Change<String>(this) {
#Override
public boolean wasAdded() {
return c.wasAdded();
}
#Override
public boolean wasRemoved() {
return c.wasRemoved();
}
#Override
public boolean wasReplaced() {
return c.wasReplaced();
}
#Override
public boolean wasUpdated() {
return c.wasUpdated();
}
#Override
public boolean wasPermutated() {
return c.wasPermutated();
}
#Override
public int getPermutation(int i) {
return c.getPermutation(i - 1) + 1;
}
#Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
#Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(String removed: c.getRemoved()) {
res.add(removed);
}
return res;
}
#Override
public int getFrom() {
return c.getFrom() + 1;
}
#Override
public int getTo() {
return c.getTo() + 1;
}
#Override
public boolean next() {
return c.next();
}
#Override
public void reset() {
c.reset();
}
});
}
};
namesListWithHeader.forEach(System.out::println);
namesListWithHeader.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown#example.com"));
namesListWithHeader.forEach(System.out::println);
}
Here's a slightly shorter version of James_D's answer using Lombok's #Delegate to avoid having to write all the delegating methods
/**
* A List that mirrors a base List by applying a converter on all items.
* #param <E> item type of the target List
* #param <F> item type of the base list
*/
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {
/** mapping function from base list item type to target list item type */
private final Function<F, E> converter;
public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
super(list);
this.converter = converter;
}
#Override
public int getSourceIndex(int index) {
return index;
}
#Override
public E get(int index) {
return converter.apply(getSource().get(index));
}
#Override
public int size() {
return getSource().size();
}
#Override
protected void sourceChanged(Change<? extends F> change) {
fireChange(new DelegatingChange(change, this));
}
/**
* An implementation of {#link Change} that delegates all methods to a specified change except {#link #getRemoved()}
*/
private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {
#Delegate(excludes = DelegatingChangeExcluded.class)
private final Change<? extends F> change;
public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
super(list);
this.change = change;
}
#Override
protected int[] getPermutation() {
return new int[0];
}
#Override
public List<E> getRemoved() {
return change.getRemoved().stream()
.map(converter)
.collect(Collectors.toList());
}
}
/**
* This interface is only used to exclude some methods from delegated methods via Lombok's #{#link Delegate}
* so that the compiler doesn't complain.
*/
#SuppressWarnings("unused")
private interface DelegatingChangeExcluded<E> {
List<E> getRemoved();
ObservableList<? extends E> getList();
List<E> getAddedSubList();
}
}

Same asked a different way this time the StringConverter is acting up

Books.java
`final class Books extends Group {
private TableView table = new TableView();
private ObservableList<Book> data;
//private ObservableList<Person> plist;
final HBox hb = new HBox();
final TextField Title = new TextField();
final TextField Author = new TextField();
final TextField Publisher = new TextField();
final TextField Copywrite = new TextField();
final TextField ISBN = new TextField();
final Boolean CheckedOut = false;
final Label Whom;
final Button addButton = new Button("Add");
Boolean FirstRead = true;
//StringConverter<Person> converter;
public final class Book {
private final SimpleStringProperty title;
private final SimpleStringProperty author;
private final SimpleStringProperty publisher;
private final SimpleStringProperty copywrite;
private final SimpleStringProperty isbn;
private final BooleanProperty checkedout;
private final SimpleStringProperty who;
Book(String Titl, String Auth, String Publ,
String Cpywrit, String IsBn, Boolean ChkdOut, String WHO) {
this.title = new SimpleStringProperty(Titl);
this.author = new SimpleStringProperty(Auth);
this.publisher = new SimpleStringProperty(Publ);
this.copywrite = new SimpleStringProperty(Cpywrit);
this.isbn = new SimpleStringProperty(IsBn);
this.checkedout = new SimpleBooleanProperty(ChkdOut);
this.who = new SimpleStringProperty(WHO);
}
public boolean isCheckedOut() {
return checkedout.get();
}
public void setCheckedOut(boolean international) {
this.checkedout.set(international);
}
public BooleanProperty isCheckedOutProperty() {
return checkedout;
}
public String getTitle() {
return title.get();
}
public void setTitle(String Title) {
title.set(Title);
}
public String getAuthor() {
return author.get();
}
public void setAutor(String Author) {
author.set(Author);
}
public String getPublisher() {
return publisher.get();
}
public void setPublisher(String Publisher) {
publisher.set(Publisher);
}
public String getCopywrite() {
return copywrite.get();
}
public void setCopywrite(String Copywrite) {
copywrite.set(Copywrite);
}
public String getIsbn() {
return isbn.get();
}
public void setIsbn(String ISBN) {
isbn.set(ISBN);
}
public Boolean getIo() {
return checkedout.get();
}
public void setIo(Boolean CheckedOut) {
checkedout.set(CheckedOut);
}
public String getWho() {
return who.get();
}
public void setWho(String Who) {
who.set(Who);
}
public String isWhoProperty() {
return getWho();
}
}
public Books(final File User) throws IOException {
this.Whom = new Label("inLibrary");
this.data = FXCollections.<Book>observableArrayList(
(Book bk) -> new Observable[]{bk.isCheckedOutProperty()
});
PhoneList list = new PhoneList(User);
final Label label = new Label("Book List");
label.setFont(new Font("Arial", 20));
table.setPrefSize(600, 400);
table.setEditable(true);
TableColumn nameCol = bookName();
TableColumn authorCol = bookAuthor();
TableColumn publisherCol = bookPublisher();
TableColumn copywriteCol = bookCopywrite();
TableColumn isbnCol = bookISBN();
TableColumn<Book, Boolean> ioCol = ioCol();
///// START work area
final TableColumn whoCol;
whoCol = new TableColumn<>("Who to");
whoCol.setMinWidth(100);
whoCol.setEditable(true);
whoCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("who"));
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(list.phonelist));
////////////////////////////////////////////////////////////////////////////////
//whoCol.setCellValueFactory(new PropertyValueFactory<>("who"));
//whoCol.setCellFactory(ComboBoxTableCell.<String, Person>forTableColumn(converter, plist));
//whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(list.phonelist));
/*
whoCol.setOnEditCommit((TableColumn.CellEditEvent<Book, String> t) -> {
((Book) t.getTableView().getItems().get(
t.getTablePosition().getRow()))
.setWho(t.getNewValue());
try {
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
});
*/
//// END work area
AddBook(nameCol, authorCol, publisherCol, copywriteCol, isbnCol, ioCol, whoCol, User);
data.addListener((javafx.collections.ListChangeListener.Change<? extends Book> change) -> {
while (change.next()) {
if (change.wasUpdated() && FirstRead != true) {
try {
System.out.println("List changed");
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
getChildren().addAll(vbox);
try {
readFile(User);
} catch (Exception ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
private TableColumn<Book, Boolean> ioCol() {
final TableColumn<Book, Boolean> ioCol = new TableColumn<>("In/Out");
ioCol.setMinWidth(50);
ioCol.setEditable(true);
ioCol.setCellValueFactory(new PropertyValueFactory<>("isCheckedOut"));
final Callback<TableColumn<Book, Boolean>, TableCell<Book, Boolean>> iocellFactory = CheckBoxTableCell.forTableColumn(ioCol);
ioCol.setCellFactory((TableColumn<Book, Boolean> column) -> {
TableCell<Book, Boolean> iocell = iocellFactory.call(column);
iocell.setAlignment(Pos.CENTER);
return iocell;
});
ioCol.setCellFactory(iocellFactory);
return ioCol;
}
private TableColumn bookISBN() {
//Column ISBN Number
TableColumn isbnCol = new TableColumn("ISBN #");
isbnCol.setMinWidth(100);
isbnCol.setCellValueFactory(
new PropertyValueFactory<>("isbn"));
isbnCol.setCellFactory(TextFieldTableCell.forTableColumn());
return isbnCol;
}
private TableColumn bookCopywrite() {
//Column Copywrite
TableColumn copywriteCol = new TableColumn("Copywrite");
copywriteCol.setMinWidth(100);
copywriteCol.setCellValueFactory(
new PropertyValueFactory<>("copywrite"));
copywriteCol.setCellFactory(TextFieldTableCell.forTableColumn());
return copywriteCol;
}
private TableColumn bookPublisher() {
//Column Publisher
TableColumn publisherCol = new TableColumn("Publisher");
publisherCol.setMinWidth(100);
publisherCol.setCellValueFactory(
new PropertyValueFactory<>("publisher"));
publisherCol.setCellFactory(TextFieldTableCell.forTableColumn());
return publisherCol;
}
private TableColumn bookAuthor() {
//Column Author
TableColumn authorCol = new TableColumn("Author");
authorCol.setMinWidth(100);
authorCol.setCellValueFactory(
new PropertyValueFactory<>("author"));
authorCol.setCellFactory(TextFieldTableCell.forTableColumn());
return authorCol;
}
private TableColumn bookName() {
// Column Name
TableColumn nameCol = new TableColumn("Title");
nameCol.setMaxWidth(100);
nameCol.setCellValueFactory(
new PropertyValueFactory<>("title"));
nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
return nameCol;
}
private void AddBook(TableColumn nameCol, TableColumn authorCol, TableColumn publisherCol,
TableColumn copywriteCol, TableColumn isbnCol, TableColumn ioCol, TableColumn whoCol, final File User) {
table.setItems(data);
table.getColumns().addAll(nameCol, authorCol, publisherCol, copywriteCol, isbnCol, ioCol, whoCol);
addButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
addBook();
try {
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void addBook() {
data.add(new Book(
Title.getText(),
Author.getText(),
Publisher.getText(),
Copywrite.getText(),
ISBN.getText(),
CheckedOut,
Whom.getText()
));
Title.clear();
Author.clear();
Publisher.clear();
Copywrite.clear();
ISBN.clear();
}
});
hb.getChildren().addAll(Title, Author, Publisher,
Copywrite, ISBN, addButton);
hb.setSpacing(10);
Title.setPromptText("Tile of Book");
Title.setMaxWidth(nameCol.getPrefWidth());
Author.setMaxWidth(authorCol.getPrefWidth());
Author.setPromptText("Author");
Publisher.setMaxWidth(publisherCol.getPrefWidth());
Publisher.setPromptText("Publisher");
Copywrite.setMaxWidth(copywriteCol.getPrefWidth());
Copywrite.setPromptText("Year Copywrite");
ISBN.setMaxWidth(isbnCol.getPrefWidth());
ISBN.setPromptText("ISBN #");
}
private void writeFile(File User) throws IOException {
File file = new File(User + "/Books.txt");
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter outFile = new PrintWriter(bw);
if (table.getItems() != null) {
data.stream().map((data1) -> {
if (data1.getTitle().equals("")) {
data1.setTitle("No_Title");
}
return data1;
}).map((data1) -> {
if (data1.getAuthor().equals("")) {
data1.setAutor("No_Author");
}
return data1;
}).map((data1) -> {
if (data1.getPublisher().equals("")) {
data1.setPublisher("No_Publisher");
}
return data1;
}).map((data1) -> {
if (data1.getCopywrite().equals("")) {
data1.setCopywrite("No_Copywrite");
}
return data1;
}).map((data1) -> {
if (data1.getIsbn().equals("")) {
data1.setIsbn("No_ISBN");
}
return data1;
}).map((data1) -> {
if (data1.getWho().equals("")) {
data1.setWho("InLibrary");
}
return data1;
}).map((data1) -> {
outFile.println(data1.getTitle());
return data1;
}).map((data1) -> {
outFile.println(data1.getAuthor());
return data1;
}).map((data1) -> {
outFile.println(data1.getPublisher());
return data1;
}).map((data1) -> {
outFile.println(data1.getCopywrite());
return data1;
}).map((data1) -> {
outFile.println(data1.getIsbn());
return data1;
}).map((data1) -> {
outFile.println(data1.getIo());
return data1;
}).forEach((data1) -> {
outFile.println(data1.getWho());
});
outFile.close();
}
}
private void readFile(File User) throws Exception {
try {
String name, author, publisher, copywrite, isbn, whom;
Boolean InOut;
try (Scanner inFile = new Scanner(new File(User + "/Books.txt"))) {
while (inFile.hasNextLine()) {
name = inFile.next();
author = inFile.next();
publisher = inFile.next();
copywrite = inFile.next();
isbn = inFile.next();
InOut = inFile.nextBoolean();
whom = inFile.next();
data.add(new Book(name, author, publisher, copywrite,
isbn, InOut, whom));
}
}
table.setItems(data);
} //insert catch statements
catch (FileNotFoundException exception) {
System.out.println("File not found");
} catch (ArrayIndexOutOfBoundsException AIOOBexception) {
System.out.println("Array Index is out of bounds");
} catch (IllegalArgumentException IAexception) {
System.out.println("Divide by zero error");
} catch (NoSuchElementException NAexception) {
}
FirstRead = false;
}
}`
PhoneList.java
`final class PhoneList extends Group {
private TableView table = new TableView();
final ObservableList<Person> phonelist = FXCollections.observableArrayList();
final HBox hb = new HBox();
public PhoneList(final File User) {
final Label label = new Label("Phone List");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMaxWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
try {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setFirstName(t.getNewValue());
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
try {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setLastName(t.getNewValue());
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
TableColumn phoneNumCol = new TableColumn("Phone Number");
phoneNumCol.setMinWidth(100);
phoneNumCol.setCellValueFactory(
new PropertyValueFactory<>("phoneNum"));
phoneNumCol.setCellFactory(TextFieldTableCell.forTableColumn());
phoneNumCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
try {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setPhoneNum(t.getNewValue());
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(120);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
emailCol.setCellFactory(TextFieldTableCell.forTableColumn());
emailCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
try {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setEmail(t.getNewValue());
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
table.setItems(phonelist);
table.getColumns().addAll(firstNameCol, lastNameCol, phoneNumCol, emailCol);
final TextField addFirstName = new TextField();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextField addPhoneNum = new TextField();
addPhoneNum.setMaxWidth(lastNameCol.getPrefWidth());
addPhoneNum.setPromptText("Phone Number");
final TextField addEmail = new TextField();
//addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPrefWidth(175);
addEmail.setPromptText("Email");
final Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
try {
phonelist.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addPhoneNum.getText(),
addEmail.getText()));
addFirstName.clear();
addLastName.clear();
addPhoneNum.clear();
addEmail.clear();
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
hb.getChildren().addAll(addFirstName, addLastName, addPhoneNum, addEmail, addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
getChildren().addAll(vbox);
try {
readFile(User);
} catch (Exception ex) {
Logger.getLogger(PhoneList.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void readFile(File User) throws Exception {
try {
String fN, lN, pNum, eMail;
try (Scanner inFile = new Scanner(new File(User + "/PhoneList.txt"))) {
while (inFile.hasNextLine()) {
fN = inFile.next();
lN = inFile.next();
pNum = inFile.next();
eMail = inFile.next();
phonelist.add(new Person(fN, lN, pNum, eMail));
}
}
table.setItems(phonelist);
} //insert catch statements
catch (FileNotFoundException exception) {
System.out.println("File not found");
} catch (ArrayIndexOutOfBoundsException AIOOBexception) {
System.out.println("Array Index is out of bounds");
} catch (IllegalArgumentException IAexception) {
System.out.println("Divide by zero error");
} catch (NoSuchElementException NAexception) {
}
}
public void writeFile(File User) throws IOException {
File file = new File(User + "/PhoneList.txt");
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter outFile = new PrintWriter(bw);
if (table.getItems() != null) {
for (int i = 0; i < phonelist.size(); i++) {
outFile.println(phonelist.get(i).getFirstName());
outFile.println(phonelist.get(i).getLastName());
if (phonelist.get(i).getPhoneNum().equals("")) {
phonelist.get(i).setPhoneNum("No_Phone");
}
if (phonelist.get(i).getEmail().equals("")) {
phonelist.get(i).setEmail("No_Email");
}
outFile.println(phonelist.get(i).getPhoneNum());
outFile.println(phonelist.get(i).getEmail());
}
outFile.close();
}
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty phoneNum;
private final SimpleStringProperty eMail;
Person(String fName, String lName, String pNum, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.phoneNum = new SimpleStringProperty(pNum);
this.eMail = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lName) {
lastName.set(lName);
}
public String getPhoneNum() {
return phoneNum.get();
}
public void setPhoneNum(String pNum) {
phoneNum.set(pNum);
}
public String getEmail() {
return eMail.get();
}
public void setEmail(String email) {
eMail.set(email);
}
public String getName() {
String Name = getFirstName() + "_" + getLastName();
return Name;
}
public Observable isWhoProperty() {
return firstName;
}
#Override
public String toString() {
String Name = getFirstName() + "_" + getLastName();
return Name;
}
}
class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
commitEdit(textField.getText());
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem();
}
}
}`
With these two I do have the ComboBoxTableCell working properly... Thank you very much. Now its time to figure out how to save it to the .txt file. Hmm can't think of anything more to say - make sure that you also get MykeZ folder and its contents. but this version has all it needs to work so you can add new data if necessary.
`final TableColumn<Book, Person> whoCol;
whoCol = new TableColumn<>("Who to");
whoCol.setMinWidth(100);
whoCol.setEditable(true);
whoCol.setCellValueFactory(new PropertyValueFactory("who"));
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(list.phonelist));
whoCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Book, Person>>() {
public void handle(TableColumn.CellEditEvent<Book, Person> evt) {
try {
evt.getRowValue().setWho(evt.getNewValue().getName());
writeFile(User);
} catch (IOException ex) {
Logger.getLogger(Books.class.getName()).log(Level.SEVERE, null, ex);
}
}
});`
And were finished Please give your self credit in this postings for the answer, am very happy thank you your the best
I don't really understand the question, but you should be doing something like this.
edit: from looking briefly at your code you might be trying to do something like this;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class ComboBoxTable extends Application {
#Override
public void start(Stage stage) {
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.addAll(new Person("Jack"),new Person("Jill"),new Person("John"));
ObservableList<Book> books = FXCollections.observableArrayList();
for (int i=1;i<5;i++)books.add(new Book(i*100,new Person("wrong guy")));
StringConverter<Person> cvrt = new StringConverter<Person>(){
#Override
public String toString(Person person) {
return person.getName();
}
#Override
public Person fromString(String string) {
return null;
}
};
TableView<Book> tv = new TableView(books);
tv.setEditable(true);
tv.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Book, Integer> numCol = new TableColumn("Book Num");
numCol.setCellValueFactory(new PropertyValueFactory("num"));
TableColumn<Book, Person> nameCol = new TableColumn("Name");
nameCol.setCellValueFactory(new PropertyValueFactory("person"));
nameCol.setCellFactory(ComboBoxTableCell.<Book, Person>forTableColumn(cvrt,persons));
tv.getColumns().addAll(numCol, nameCol);
StackPane root = new StackPane(tv);
Scene scene = new Scene(root, 300, 250);
stage.setScene(scene);
stage.show();
}
public class Person{
SimpleStringProperty name;
public Person(String name) {
this.name = new SimpleStringProperty(name);
}
public SimpleStringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
//in simple cases you can just do this and not use a string converter
#Override
public String toString(){
return name.get();
}
}
public class Book{
SimpleIntegerProperty num;
SimpleObjectProperty<Person> person;
public Book(int num, Person person) {
this.num = new SimpleIntegerProperty(num);
this.person = new SimpleObjectProperty<>(person);
}
public SimpleIntegerProperty numProperty() {
return num;
}
public SimpleObjectProperty<Person> personProperty() {
return person;
}
}
}
Look at the TableView, TableColumn, and ComboBoxTableCell documentation and pay attention to the <S,T>
The reason you're getting a reference to Person is because the ComboBox doesn't know how to convert it to a string. I put a simple bit of code to override the toString() method for the Person class. If you don't specify a StringConverter it won't know how to convert and just uses toString() for objects.
Edit again: Look at your Book class, you have StringProperty who and you're trying to store a person in that field. Look at my code and I use an ObjectProperty<Person>.
You can do it your way but override the toString() in the Person class like in my example and don't use a StringConverter.
#Override
public String toString(){
return name.get();
}
Then make your TableColumn like this and use the type <Book, Person>
TableColumn<Book, Person> whoCol = new TableColumn<>("Who to");
whoCol.setMinWidth(100);
//editable is true for columns by default.
whoCol.setCellValueFactory(new PropertyValueFactory("who"));
whoCol.setCellFactory(ComboBoxTableCell.forTableColumn(p.phonelist));
whoCol.setOnEditCommit((evt) -> {
evt.getRowValue().setWho(evt.getNewValue().getName());
});
I made an onEditCommit so the Book class will know how to store the Person info.

Resources