I am currently trying to write my first JavaFX app that writes messages decoded from a socket into a JavaFX Table. The table needs to be dynamic and if a message has data that currently doesn't exist in the table a new column should be added. Some of the fields are nested in a wrapper class and I was trying to represent these nesting using a Nested Column as seen here: http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJABHBEH
My issue is I cannot seem to get the correct data to bind to the nested columns. When I try binding to PropertyValueFactory("one") for my CustomType I am getting Optionals from MessageType1. I have tried a couple of different things as you will see in my sample below:
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
public class SampleTable extends Application {
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final ObservableList<Message> msgList = FXCollections.observableArrayList();
public static void main(final String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
final TableView tableView = new TableView<>(msgList);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
final TableColumn<Message, MessageType> msgTypeCol = new TableColumn<Message, MessageType>();
msgTypeCol.setText("Message Type");
msgTypeCol.setCellValueFactory(new PropertyValueFactory<Message, MessageType>("messageType"));
final TableColumn<MessageType1, Optional<Integer>> intCol1 = new TableColumn<MessageType1, Optional<Integer>>();
intCol1.setText("Int1");
intCol1.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("one"));
final TableColumn<MessageType1, Optional<Integer>> intCol2 = new TableColumn<MessageType1, Optional<Integer>>();
intCol2.setText("Int2");
intCol2.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("two"));
final TableColumn<MessageType1, Optional<Integer>> intCol3 = new TableColumn<MessageType1, Optional<Integer>>();
intCol3.setText("Int3");
intCol3.setCellValueFactory(new PropertyValueFactory<MessageType1, Optional<Integer>>("three"));
final TableColumn<MessageType2, List<String>> strCol = new TableColumn<MessageType2, List<String>>();
strCol.setText("String List");
strCol.setCellValueFactory(new PropertyValueFactory<MessageType2, List<String>>("list"));
//final TableColumn<MessageType3, Optional<CustomType>> customTypeCol = new TableColumn<MessageType3, Optional<CustomType>>();
final TableColumn customTypeCol = new TableColumn<>();
customTypeCol.setText("Custom Type");
// final TableColumn<CustomType, Boolean> customTypeCol1 = new TableColumn<CustomType, Boolean>();
// customTypeCol1.setText("CT1");
// customTypeCol1.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("one"));
// final TableColumn<CustomType, Boolean> customTypeCol2 = new TableColumn<CustomType, Boolean>();
// customTypeCol2.setText("CT2");
// customTypeCol2.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("customTwo"));
// final TableColumn<CustomType, Integer> customTypeCol3 = new TableColumn<CustomType, Integer>();
// customTypeCol3.setText("CT3");
// customTypeCol3.setCellValueFactory(new PropertyValueFactory<CustomType,Integer>("customThree"));
final TableColumn<MessageType3, Boolean> customTypeCol1 = new TableColumn<MessageType3, Boolean>();
customTypeCol1.setText("CT1");
customTypeCol1.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("one"));
final TableColumn<MessageType3, Boolean> customTypeCol2 = new TableColumn<MessageType3, Boolean>();
customTypeCol2.setText("CT2");
customTypeCol2.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("customTwo"));
final TableColumn<MessageType3, Integer> customTypeCol3 = new TableColumn<MessageType3, Integer>();
customTypeCol3.setText("CT3");
customTypeCol3.setCellValueFactory(new PropertyValueFactory<MessageType3,Integer>("customThree"));
customTypeCol.getColumns().addAll(customTypeCol1, customTypeCol2, customTypeCol3);
// customTypeCol.setCellValueFactory(new PropertyValueFactory<MessageType3, Optional<CustomType>>("customType"));
// customTypeCol.setCellFactory(
// new Callback<TableColumn<MessageType3, Optional<CustomType>>, TableCell<MessageType3, Optional<CustomType>>>() {
// @Override
// public TableCell<MessageType3, Optional<CustomType>> call(
// final TableColumn<MessageType3, Optional<CustomType>> param) {
// return new TableCell<MessageType3, Optional<CustomType>>() {
// @Override
// protected void updateItem(Optional<CustomType> customType, boolean empty) {
// super.updateItem(customType, empty);
//
// if(!empty) {
// //Tried this but didn't work either
//// final TableColumn<CustomType, Boolean> customTypeCol1 = new TableColumn<CustomType, Boolean>();
//// customTypeCol1.setText("CT1");
//// customTypeCol1.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("one"));
//// final TableColumn<CustomType, Boolean> customTypeCol2 = new TableColumn<CustomType, Boolean>();
//// customTypeCol2.setText("CT2");
//// customTypeCol2.setCellValueFactory(new PropertyValueFactory<CustomType, Boolean>("customTwo"));
//// final TableColumn<CustomType, Integer> customTypeCol3 = new TableColumn<CustomType, Integer>();
//// customTypeCol3.setText("CT3");
//// customTypeCol3.setCellValueFactory(new PropertyValueFactory<CustomType,Integer>("customThree"));
// //
// final TableColumn<MessageType3, Boolean> customTypeCol1 = new TableColumn<MessageType3, Boolean>();
// customTypeCol1.setText("CT1");
// customTypeCol1.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("one"));
// final TableColumn<MessageType3, Boolean> customTypeCol2 = new TableColumn<MessageType3, Boolean>();
// customTypeCol2.setText("CT2");
// customTypeCol2.setCellValueFactory(new PropertyValueFactory<MessageType3, Boolean>("customTwo"));
// final TableColumn<MessageType3, Integer> customTypeCol3 = new TableColumn<MessageType3, Integer>();
// customTypeCol3.setText("CT3");
// customTypeCol3.setCellValueFactory(new PropertyValueFactory<MessageType3,Integer>("customThree"));
//
// param.getColumns().addAll(customTypeCol1, customTypeCol2, customTypeCol3);
// }
// }
// };
// }
// });
tableView.getColumns().addAll(msgTypeCol, intCol1, intCol2, intCol3, strCol, customTypeCol);
final Group root = new Group();
primaryStage.setScene(new Scene(root));
root.getChildren().add(tableView);
//Simulate incoming DUMMY Messages
executor.scheduleAtFixedRate(new Runnable() {
private int i = 0;
@Override
public void run() {
switch(i) {
case 0:
msgList.add(new MessageType1());
i++;
break;
case 1:
msgList.add(new MessageType2());
i++;
break;
//System.exit(0);
case 2:
msgList.add(new MessageType3());
i = 0;
break;
}
}
}, 0, 1, TimeUnit.SECONDS);
primaryStage.show();
}
public enum MessageType { TYPE1, TYPE2, TYPE3 };
public interface Message {
MessageType getMessageType();
}
public static final class CustomType {
private final boolean one = true;
private final boolean customTwo = true;
private final int customThree = 3;
public Boolean getOne() { return one; }
public Boolean getCustomTwo() { return customTwo; }
public Integer getCustomThree() { return customThree; }
}
public static final class MessageType1 implements Message {
private final List<Optional<Integer>> intList =
ImmutableList.<Optional<Integer>>of(Optional.of(1),
Optional.of(2),
Optional.<Integer>absent());
@Override
public MessageType getMessageType() { return MessageType.TYPE1; }
public Optional<Integer> getOne() { return intList.get(0); }
public Optional<Integer> getTwo() { return intList.get(1); }
public Optional<Integer> getThree() { return intList.get(2); }
}
public static final class MessageType2 implements Message {
List<String> stringList = ImmutableList.of("one", "two", "three");
@Override
public MessageType getMessageType() { return MessageType.TYPE2; }
public List<String> getList() { return stringList; }
}
public static final class MessageType3 implements Message {
private final Optional<CustomType> obj = Optional.of(new CustomType());
@Override
public MessageType getMessageType() { return MessageType.TYPE3; }
public Optional<CustomType> getCustomType() { return obj; }
}
}
I guess the question boils down to how can I bind my CustomType data to the nested columns? Everything I tried seems to have failed. My gut feeling tells me it has something to do with dropping the generics on my TableColumns before inserting them but I don't see another option. Is there a better recommendation on how to write a table with intermixed data like this example? Maybe I'm attacking this problem in the completely wrong way?
Thanks!
Note: I wrote this code to attempt to show what I was trying to accomplish it is by no means "production" level.