i need to show on a TableView a column that is a nested bean like this:
public class A_Bean {
private ObjectProperty<BigDecimal> id;
private B_Bean qwerty;
}
public class B_Bean {
private ObjectProperty<BigDecimal> id;
private StringProperty qwerty_B;
}
All the bean class has the get, set and property method. My problem is that A_Bean has a variable of B_Bean type, and i don't know how to tell to the tableColumn that it must display the qwerty_B field of B_Bean and not the pointer to the qwerty variable of A_Bean.
#FXML
private TableView<A_Bean> myTable;
#FXML
private TableColumn<A_Bean, BigDecimal> idColumn;
#FXML
private TableColumn<A_Bean, B_Bean> qwertyColumn;
.....
#FXML
private void initialize() {
idColumn.setCellValueFactory(cellData -> cellData.getValue().idProperty() );
qwertyColumn.setCellValueFactory(cellData -> cellData.getValue().qwertyProperty() );
.....
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
myTable.setItems(mainApp.getA_BeanData());
}
If if run my code i'll see on the tableColumn something like xx.B_Bean#1234dasf23
I need to tell to the tableColumn that it must fetch the qwerty_B field from the A_Bean. How can i do that?
Just use a cell factory to tell the cell how to display the B_Bean it contains as its value:
qwertyColumn.setCellFactory(tc -> new TableCell<A_Bean, B_Bean>() {
#Override
protected void updateItem(B_Bean item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item.getQwerty_B());
}
});
Related
I'm trying to do some reports by hiding some rows from a tableview (I made in scenebuilder) by using a checkbox. If the object from tableview doesn't have in the column a specific String, it becomes invisible. example: By clicking the checkbox, hide all rows that dont have the exact value "Barcelona" in table_adress. I tried to do this, but I keep complicating myself. Any ideas?
the function
#FXML
void CakeRequestsFromBarcelona(ActionEvent event) throws IOException {
for(Object cake:table.getItems()) {
for (TableColumn column : table.getColumns()) {
//not sure how to write it
}
}
}
initialize
#FXML
private TableColumn<CakeRequest, Integer> table_ID;
#FXML
private TableColumn<CakeRequest, String> table_adress;
#FXML
private TableColumn<CakeRequest, String> table_design;
#FXML
private TableColumn<CakeRequest, String> table_flavour;
#FXML
private TableColumn<CakeRequest, String> table_model;
#FXML
private TableColumn<CakeRequest, String> table_name;
#FXML
private TableColumn<CakeRequest, String> table_phonenumber;
#FXML
private TableView<CakeRequest> table;
public void initialize(){
//cakeRequest
table_ID.setCellValueFactory(
p-> new SimpleIntegerProperty(p.getValue().getID()).asObject()
);
table_name.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getOwnerName())
);
table_adress.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getOwnerAddress())
);
table_phonenumber.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getPhoneNumber())
);
table_flavour.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getFlavour())
);
table_design.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getDesign())
);
table_model.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().getModel())
);
}
CakeRequest
public class CakeRequest implements Identifiable<Integer>, Serializable{
private int ID;
private String OwnerName;
private String OwnerAddress;
private String PhoneNumber;
private String Model;
private String Flavour;
private String Design;
Use a FilteredList and change the predicate when the check box is checked/unchecked:
private ObservableList<CakeRequest> data = FXCollections.observableArrayList();
private FilteredList<CakeRequest> filteredData = new FilteredList<>(data, request -> true);
public void initialize(){
// existing code...
table.setItems(filteredData);
}
#FXML
void cakeRequestsFromBarcelona(ActionEvent event) {
if (checkBox.isChecked()) {
filteredData.setPredicate(request -> request.getOwnerAddress().contains("Barcelona"));
} else {
filteredData.setPredicate(request -> true);
}
}
I have a JavaFX desktop app with a TableView. I populate the data using a POJO named Orders which ultimately comes from a Firebird SQL database.
Image of what I have now
What I am looking to do is change the background fill color of each cell in the first column 'Status' depending on the text value. So if the text value is 'READY' then green, 'STARTED' will be yellow and 'DONE' will be gray.
Image of what I would like
Here is the code portion I use to populate the TableView:
`
#FXML private TableView<Orders> tblOrders;
#FXML private TableColumn<Orders, Integer> clmStatus;
#FXML private TableColumn<Orders, String> clmStartDateTime;
#FXML private TableColumn<Orders, String> clmShopOrder;
#FXML private TableColumn<Orders, String> clmRotation;
#FXML private TableColumn<Orders, String> clmGMIECode;
#FXML private TableColumn<Orders, String> clmSAPCode;
#FXML private TableColumn<Orders, Integer> clmLineName;
#FXML private TableColumn<Orders, Integer> clmOrderProductionNr;
private ObservableList<Orders> list;
public void initialize(URL location, ResourceBundle resources) {
populateTable();
}
private void populateTable() {
log.appLog("Populating table\r\n");
clmStatus.setCellValueFactory(new PropertyValueFactory<>("status"));
clmStartDateTime.setCellValueFactory(new PropertyValueFactory<>
("startDateTime"));
clmShopOrder.setCellValueFactory(new PropertyValueFactory<>("extra1"));
clmRotation.setCellValueFactory(new
PropertyValueFactory<("batchLotNr"));
clmGMIECode.setCellValueFactory(new PropertyValueFactory<>("wareNr"));
clmSAPCode.setCellValueFactory(new PropertyValueFactory<>
("serviceDescription"));
clmLineName.setCellValueFactory(new PropertyValueFactory<>
("productionLineNr"));
clmOrderProductionNr.setCellValueFactory(new PropertyValueFactory<>
("orderProductionNr"));
tblOrders.setItems(list);
}
`
Code sample of my Orders POJO:
`
public class Orders {
private final SimpleStringProperty status;
private final SimpleStringProperty startDateTime;
private final SimpleStringProperty extra1;
private final SimpleStringProperty batchLotNr;
private final SimpleStringProperty wareNr;
private final SimpleStringProperty serviceDescription;
private final SimpleStringProperty productionLineNr;
private final SimpleIntegerProperty orderProductionNr;
Orders(String status, String startDateTime, String extra1, String batchLotNr, String wareNr, String serviceDescription, String productionLineNr, int orderProductionNr) {
this.status = new SimpleStringProperty(status);
this.startDateTime = new SimpleStringProperty(startDateTime);
this.extra1 = new SimpleStringProperty(extra1);
this.batchLotNr = new SimpleStringProperty(batchLotNr);
this.wareNr = new SimpleStringProperty(wareNr);
this.serviceDescription = new SimpleStringProperty(serviceDescription);
this.productionLineNr = new SimpleStringProperty(productionLineNr);
this.orderProductionNr = new SimpleIntegerProperty((orderProductionNr));
}
public String getStatus() {
return status.get();
}
public String getStartDateTime() {return startDateTime.get(); }
public String getExtra1() {
return extra1.get();
}
public String getBatchLotNr() {
return batchLotNr.get();
}
public String getWareNr() {
return wareNr.get();
}
public String getServiceDescription() {
return serviceDescription.get();
}
public String getProductionLineNr() {
return productionLineNr.get();
}
int getOrderProductionNr() {return orderProductionNr.get();}
}
`
I have tried using a callback but I have never used callbacks before and don't properly understand how I can fit my needs into a callback. Any help will be important to my learning. Thanks SO.
You have to define a custom TableCell for your status column like this:
public class ColoredStatusTableCell extends TableCell<TableRow, Status> {
#Override
protected void updateItem(Status item, boolean empty) {
super.updateItem(item, empty);
if (empty || getTableRow() == null) {
setText(null);
setGraphic(null);
} else {
TableRow row = (TableRow) getTableRow().getItem();
setText(item.toString());
setStyle("-fx-background-color: " + row.getColorAsString());
// If the statis is changing dynamic you have to add the following:
row.statusProperty()
.addListener((observable, oldValue, newValue) ->
setStyle("-fx-background-color: " + row.getColorAsString()));
}
}
}
Where TableRow:
public class TableRow {
private ObjectProperty<Status> status;
private Map<Status, Color> statusColor;
public TableRow(Status status, Map<Status, Color> statusColor) {
this.status = new SimpleObjectProperty<>(status);
this.statusColor = statusColor;
}
public Status getStatus() {
return status.get();
}
public ObjectProperty<Status> statusProperty() {
return status;
}
public Color getStatusColor() {
return statusColor.get(status.get());
}
public String getColorAsString() {
return String.format("#%02X%02X%02X",
(int) (getStatusColor().getRed() * 255),
(int) (getStatusColor().getGreen() * 255),
(int) (getStatusColor().getBlue() * 255));
}
}
Status:
public enum Status {
READY, STARTED, DONE
}
and the controller:
public class TestController {
#FXML
private TableView<TableRow> table;
#FXML
private TableColumn<TableRow, Status> column;
private ObservableList<TableRow> data = FXCollections.observableArrayList();
#FXML
public void initialize() {
column.setCellValueFactory(data -> data.getValue().statusProperty());
column.setCellFactory(factory -> new ColoredStatusTableCell());
Map<Status, Color> statusColor = new HashMap<>();
statusColor.put(Status.READY, Color.GREEN);
statusColor.put(Status.STARTED, Color.YELLOW);
statusColor.put(Status.DONE, Color.GRAY);
TableRow ready = new TableRow(Status.READY, statusColor);
TableRow started = new TableRow(Status.STARTED, statusColor);
TableRow done = new TableRow(Status.DONE, statusColor);
data.addAll(ready, started, done);
table.setItems(data);
}
}
I chose to set the status as an enum because it is easier to handle it,
then I have used a map to each status-color combination, then in the cell you can set its background color to the matched color of the status.
If you want of course instead of Color.YELLOW and so on you can use a custom Color.rgb(red,green,blue)
I finally found the solution without having to use any extra classes, just a callback in my controller class with the help of this SO link:
StackOverFlow Link
`
private void populateTable() {
log.appLog("Populating table\r\n");
//clmStatus.setCellValueFactory(new PropertyValueFactory<>("status"));
clmStatus.setCellFactory(new Callback<TableColumn<Orders, String>,
TableCell<Orders, String>>()
{
#Override
public TableCell<Orders, String> call(
TableColumn<Orders, String> param) {
return new TableCell<Orders, String>() {
#Override
protected void updateItem(String item, boolean empty) {
if (!empty) {
int currentIndex = indexProperty()
.getValue() < 0 ? 0
: indexProperty().getValue();
String clmStatus = param
.getTableView().getItems()
.get(currentIndex).getStatus();
if (clmStatus.equals("READY")) {
setTextFill(Color.WHITE);
setStyle("-fx-font-weight: bold");
setStyle("-fx-background-color: green");
setText(clmStatus);
} else if (clmStatus.equals("STARTED")){
setTextFill(Color.BLACK);
setStyle("-fx-font-weight: bold");
setStyle("-fx-background-color: yellow");
setText(clmStatus);
} else if (clmStatus.equals("DONE")){
setTextFill(Color.BLACK);
setStyle("-fx-font-weight: bold");
setStyle("-fx-background-color: gray");
setText(clmStatus);
} else {
setTextFill(Color.WHITE);
setStyle("-fx-font-weight: bold");
setStyle("-fx-background-color: red");
setText(clmStatus);
}
}
}
};
}
});
clmStartDateTime.setCellValueFactory(new PropertyValueFactory<>("startDateTime"));
clmShopOrder.setCellValueFactory(new PropertyValueFactory<>("extra1"));
clmRotation.setCellValueFactory(new PropertyValueFactory<>("batchLotNr"));
clmGMIECode.setCellValueFactory(new PropertyValueFactory<>("wareNr"));
clmSAPCode.setCellValueFactory(new PropertyValueFactory<>("serviceDescription"));
clmLineName.setCellValueFactory(new PropertyValueFactory<>("productionLineNr"));
clmOrderProductionNr.setCellValueFactory(new PropertyValueFactory<>("orderProductionNr"));
tblOrders.setItems(list);
}
`
I don't have badge to comment, but wanted to add some details.
I wanted to format color of cell based on the boolean value which i have in my data set. I have reviewed this question and similar one provided already here:
Stackoverflow link - style based on another cell in row
What was missing in both for me is reseting style when there is no value as kleopatra mentioned.
This works for me:
public class TableCellColored extends TableCell<DimensionDtoFxBean, DimValVoFxBean> {
private static final String DEFAULT_STYLE_CLASS = "table-cell";
public TableCellColored() {
super();
}
#Override
protected void updateItem(DimValVoFxBean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
resetStyle();
return;
}
setText(Optional.ofNullable(item.getValue()).map(BigDecimal::toString).orElse(""));
Boolean conversionFlag = Optional.ofNullable(item.getConversionFlag()).orElse(true);
updateStyle(conversionFlag);
item.conversionFlagProperty()
.addListener((observable, oldValue, newValue) -> updateStyle(newValue));
}
private void updateStyle(Boolean conversionFlag) {
if (!conversionFlag) {
setStyle("-fx-background-color: red");
} else {
resetStyle();
}
}
private void resetStyle() {
setStyle("");
getStyleClass().addAll(TableCellColored.DEFAULT_STYLE_CLASS);
}
}
Since I have value object with value and boolean flag I can do it i seperate class and don't have add lambda in controller.
Deafult styling of cell is transparent so if we use style to change color, we have to reset it when there is no value.
Since direct styling has bigger priority than class it overrides default styling from css classes.
To be on the safe side I also apply DEFAULT_STYLE_CLASS. Value taken from TableCell class.
Without listener and styles reset I red was staying in table during scrolling. After few scrolls all cells where red. So listener and styles reset is the must have for me.
I have a JavaFX ListView. When I click on an item in the list, I would like like two...edit controls, a ComboBox and a TextField, to be populated by the appropriate values from the model.
First, my model:
public class Recipient {
private final SimpleStringProperty type = new SimpleStringProperty();
private final SimpleStringProperty address = new SimpleStringProperty();
// property getters
}
In my controller, I have:
public class Controller implements Initializable {
#FXML
private ComboBox type;
#FXML
private TextField address;
#FXML
private ListView<Recipient> recipList;
private final ObservableList<String> types = FXCollections.observableArrayList("SMS", "Email");
private final ObservableList<Recipient> recips = FXCollections.observableArrayList(Recipient.DUMMYDATA);
private final ObjectProperty<Recipient> recipient = new SimpleObjectProperty<>();
#Override
public void initialize(URL url, ResourceBundle rb) {
type.setItems(types);
recipList.setItems(recips);
recipList.setCellFactory((ListView<Recipient> p) -> new ListCell<Recipient>() {
#Override
public void updateItem(Recipient recip, boolean empty) {
super.updateItem(recip, empty);
final int index = p.getItems().indexOf(recip);
if (index > -1) {
setText(String.format("%s - %s", recip.typeProperty().get(), recip.addressProperty().get()));
} else {
setText(null);
}
}
});
recipient.setValue(new Recipient());
recipList.setOnMouseClicked(event -> recipClicked(event));
type.valueProperty().bindBidirectional(recipient.get().typeProperty());
address.textProperty().bindBidirectional(recipient.get().addressProperty());
}
public void recipClicked(MouseEvent event) {
final MultipleSelectionModel<Recipient> get = recipList.selectionModelProperty().get();
final Recipient selectedItem = get.getSelectedItem();
recipient.setValue(selectedItem);
}
}
When I click on the list, the SimpleObjectProperty is updated as expected, but my controls do now show the data. What am I missing?
Your bindings bind to the properties belonging to the current recipient at the time the bindings are made. If the value of recipient changes, then, for example, address.textProperty will still be bound to the addressProperty() of the previous value of recipient, not the new one.
You can use a listener on recipient to bind and unbind the controls:
recipient.addListener((obs, oldRecipient, newRecipient) -> {
if (oldRecipient != null) {
type.valueProperty().unbindBidirectional(oldRecipient.typeProperty());
address.textProperty().unbindBidirectional(oldRecipient.addressProperty());
}
if (newRecipient != null) {
type.valueProperty().bindBidirectional(newRecipient.typeProperty());
address.textProperty().bindBidirectional(newRecipient.addressProperty());
}
});
As an aside, note that you should not use a mouse listener to respond to changes in selection: it will not work, for example, if the user uses the keyboard to change selection in the list view. You can replace recipList.setOnMouseClicked(...) with
recipient.bind(recipList.getSelectionModel().selectedItemProperty());
and remove recipClicked(...) entirely. (In fact, you might not need recipient at all: you can just replace it with recipList.getSelectionModel().selectedItemProperty().)
public class Controller implements Initializable {
#FXML
private ComboBox type;
#FXML
private TextField address;
#FXML
private ListView<Recipient> recipList;
private final ObservableList<String> types = FXCollections.observableArrayList("SMS", "Email");
private final ObservableList<Recipient> recips = FXCollections.observableArrayList(Recipient.DUMMYDATA);
#Override
public void initialize(URL url, ResourceBundle rb) {
type.setItems(types);
recipList.setItems(recips);
recipList.setCellFactory((ListView<Recipient> p) -> new ListCell<Recipient>() {
#Override
public void updateItem(Recipient recip, boolean empty) {
super.updateItem(recip, empty);
if (empty) {
setText(null);
} else {
setText(String.format("%s - %s", recip.typeProperty().get(), recip.addressProperty().get()));
}
}
});
recipList.getSelectionModel().selectedItemProperty().addListener((obs, oldRecipient, newRecipient) -> {
if (oldRecipient != null) {
type.valueProperty().unbindBidirectional(oldRecipient.typeProperty());
address.textProperty().unbindBidirectional(oldRecipient.addressProperty());
}
if (newRecipient != null) {
type.valueProperty().bindBidirectional(newRecipient.typeProperty());
address.textProperty().bindBidirectional(newRecipient.addressProperty());
}
});
}
}
So I have the following code:
public class ToDoListController implements Initializable {
String text = "";
LocalDate isoDate;
#FXML
private Button bttnAddEvent;
#FXML
private DatePicker pickerDate;
#FXML
private ComboBox<String> eventsSelector;
#FXML
private Button bttnDone;
#FXML
private Button bttnRemove;
#FXML
private TableView<EventsBean> eventsTable;
#FXML
private TableColumn<EventsBean, String> eventCol;
#FXML
private TableColumn<EventsBean, LocalDate> dateCol;
#FXML
private TableColumn<?, ?> doneCol;
#FXML
private TableColumn<?, ?> observationCol;
#FXML
private TableColumn<?, ?> removeCol;
#FXML
void bttnDoneAction(ActionEvent event) {
}
#FXML
void bttnRemoveAction(ActionEvent event) {
}
#FXML
void pickerDateAction(ActionEvent event) {
isoDate = pickerDate.getValue();
}
ObservableList<EventsBean> dataList =
FXCollections.observableArrayList();
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("The pane loaded");
List<String> myList;
try {
myList = Files.lines(Paths.get("src/com/todolist/EventsList.txt")).collect(Collectors.toList());
eventsSelector.setItems(FXCollections.observableArrayList(myList));
} catch (IOException e) {
System.out.println("Don t find file");
}
eventCol.setCellValueFactory(new PropertyValueFactory<EventsBean, String>("event"));
dateCol.setCellValueFactory(new PropertyValueFactory<EventsBean, LocalDate>("date"));
observationCol.setCellValueFactory(new PropertyValueFactory<EventsBean, String>("observation"));
observationCol.setCellFactory(TextFieldTableCell.<EventsBean>forTableColumn());
observationCol.setOnEditCommit(
(CellEditEvent<EventsBean, String> t) -> {
((EventsBean) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setObservation(t.getNewValue());
});
observationCol.setSortable(false);
eventsTable.setItems(dataList);
eventsTable.setEditable(true);
bttnAddEvent.setOnAction((ActionEvent e) -> {
text = eventsSelector.getValue().toString();
dataList.add(new EventsBean(text, isoDate, ""));
});
}
}
and my EventsBean class:
public class EventsBean {
private SimpleStringProperty observation;
private SimpleStringProperty event;
private SimpleObjectProperty<LocalDate> date;
public EventsBean(String event, LocalDate date, String observation) {
this.event = new SimpleStringProperty(event);
this.date = new SimpleObjectProperty<LocalDate>(date);
this.observation = new SimpleStringProperty(observation);
}
public String getEvent() {
return event.get();
}
public LocalDate getDate() {
return date.get();
}
public String getObservation() {
return observation.get();
}
public void setObservation(String observation) {
this.observation.set(observation);
}
}
As you can see, I populate the ComboBox with data from a text file.
So I want to use Button bttnAddEvent to add a selected data from ComboBox in TableColumn <?,?> eventCol. Beside this, another challenge it is to use the same Button bttnAddEvent in the same time with data from ComboBox to add the selected date from DatePicker pickerDate in TableColumn <?,?> dateCol.
Update: Until now I put the value of ComboBox into a String named text, but how to transfer into TableColumn eventCol this value from text?
Thank you very much!
I've found the solution and updated the code for someone who will meet this need.
How can I fill ChoiceBox with e.g. a StringProperty from my custom class?
I have simply design in SceneBuilder with a ChoiceBox and I have a Person class with my data.
public class Person{
private final StringProperty firstName;
public Person(){
this(null);
}
public Person(String fname){
this.firstName = new SimpleStringProperty(fname);
}
public String getFirstName(){
return this.firstName.get();
}
public void setFirstName(String fname){
this.firstName.set(fname);
}
public StringProperty firstNameProperty(){
return this.firstName;
}
}
In main class I have:
private ObservableList<Person> personList = FXCollections.observableArrayList();
this.personList.add(new Person("Human1"));
RootController controller = loader.getController();
controller.setChoiceBox(this);
public ObservableList<Person> getPersonList(){
return this.personList;
}
And in my controller:
public class RootController {
#FXML
private ChoiceBox personBox;
public RootController(){
}
#FXML
private void initialize(){
}
public void setChoiceBox(App app){
personBox.setItems(app.getPersonList());
}
}
But this code fill my ChoiceBox by function name(??) or something like that.
How can I fill it with the firstName property?
Note that you've created yourself a big problem by making the firstName property mutable here.
AFAIK it's not possible to make ChoiceBox listen to modifications of that property (at least not without replacing the skin, which would be awfully complicated).
This could be done with a ComboBox however.
You just need to use a custom cellFactory:
private ListCell<Person> createCell(ListView<Person> listView) {
return new ListCell<Person>() {
#Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
textProperty().unbind();
setText("");
} else {
textProperty().bind(item.firstNameProperty());
}
}
};
}
ComboBox<Person> cb = new ComboBox<>(personList);
cb.setCellFactory(this::createCell);
cb.setButtonCell(createCell(null));
...
For your problem I would suggest to use the 'easiest way'.The ChoiceBox uses the toString() method of the Person class resulting in something like choiceBox.Person#18f8865.
By overriding the toString() method you can define what the ChoiceBox will display. In your case return the value of the firstName property:
#Override
public String toString() {
return firstName.get();
}