Can't bind JavaFX TextField to ListView value - javafx

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());
}
});
}
}

Related

Bind CheckBoxTableCell to BooleanBinding

I want to bind a CheckBox in a TableViewCell to a BooleanBinding. The following sample consists of a TableView with a column name and isEffectiveRequired. The checkbox in the column is bound to the Expression:
isRequired.or(name.isEqualTo("X"))
So an item is "effectivly required" when the item in the row is required OR the name is an X, then the expression should be true.
Unfortunately the CheckBox does not reflect the change. For debugging I added a textfield, showing the nameProperty, requiredProperty and the computed effectiveRequiredProperty.
Interestingly when returning just the isRequiredProperty instead of the binding the checkbox works.
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
So what is the difference between a Property and a ObservableValue in regard to a CheckBox?
public class TableCellCBBinding extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
private void init(Stage primaryStage) {
primaryStage.setScene(new Scene(buildContent()));
}
private Parent buildContent() {
TableView<ViewModel> tableView = new TableView<>();
tableView.setItems(sampleEntries());
tableView.setEditable(true);
tableView.getColumns().add(buildRequiredColumn());
tableView.getColumns().add(buildNameColumn());
// Add a Textfield to show the values for the first item
// As soon as the name is set to "X", the effectiveRequiredProperty should evaluate to true and the CheckBox should reflect this but it does not
TextField text = new TextField();
ViewModel firstItem = tableView.getItems().get(0);
text.textProperty()
.bind(Bindings.format("%s | %s | %s", firstItem.nameProperty(), firstItem.isRequiredProperty(), firstItem.effectiveRequiredProperty()));
return new HBox(text, tableView);
}
private TableColumn<ViewModel, String> buildNameColumn() {
TableColumn<ViewModel, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
return nameColumn;
}
private TableColumn<ViewModel, Boolean> buildRequiredColumn() {
TableColumn<ViewModel, Boolean> requiredColumn = new TableColumn<>("isEffectiveRequired");
requiredColumn.setMinWidth(50);
// This is should bind my BindingExpression from to ViewModel to the CheckBox
requiredColumn.setCellValueFactory( p -> p.getValue().effectiveRequiredProperty());
requiredColumn.setCellFactory( CheckBoxTableCell.forTableColumn(requiredColumn));
return requiredColumn;
}
private ObservableList<ViewModel> sampleEntries() {
return FXCollections.observableArrayList(
new ViewModel(false, "A"),
new ViewModel(true, "B"),
new ViewModel(false, "C"),
new ViewModel(true, "D"),
new ViewModel(false, "E"));
}
public static class ViewModel {
public static final String SPECIAL_STRING = "X";
private final StringProperty name;
private final BooleanProperty isRequired;
public ViewModel(boolean isRequired, String name) {
this.name = new SimpleStringProperty(this, "name", name);
this.isRequired = new SimpleBooleanProperty(this, "isRequired", isRequired);
this.name.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
}
public StringProperty nameProperty() {return name;}
public final String getName(){return name.get();}
public final void setName(String value){
name.set(value);}
public boolean isRequired() {
return isRequired.get();
}
public BooleanProperty isRequiredProperty() {
return isRequired;
}
public void setRequired(final boolean required) {
this.isRequired.set(required);
}
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
}
}
When typing an X into the name the checkbox in the row should be checked.
When typing an X into the name the checkbox in the row is not checked. It's never checked like it is not bound at all.
CheckBoxXXCells don't live up to their doc when it comes to binding their selected state, f.i. (citing here just for signature, even if not set explicitely):
public final Callback <Integer,​ObservableValue<Boolean>> getSelectedStateCallback()
Returns the Callback that is bound to by the CheckBox shown on screen.
clearly talks about an ObservableValue, so we would expect that it at least shows the selection state.
Actually, the implementation does exactly nothing if it's not a property, the relevant part from its updateItem:
StringConverter<T> c = getConverter();
if (showLabel) {
setText(c.toString(item));
}
setGraphic(checkBox);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
To work around, use a custom cell that updates the selected state in its updateItem. With the added quirk that we need to disable the check's firing to really keep the visuals in sync with backing state:
requiredColumn.setCellFactory(cc -> {
TableCell<ViewModel, Boolean> cell = new TableCell<>() {
CheckBox check = new CheckBox() {
#Override
public void fire() {
// do nothing - visualizing read-only property
// could do better, like actually changing the table's
// selection
}
};
{
getStyleClass().add("check-box-table-cell");
check.setOnAction(e -> {
e.consume();
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
check.setSelected(item);
setGraphic(check);
}
}
};
return cell;
});

JavaFX static ObservableList not refreshing ComboBox

What I'm trying to do is have a single class that maintains a static ObservableList of countries. I want to display these countries in a ComboBox. I've got this part working fine. Now, I also want to enable the user to add new countries to the list. So, there is a button beside the combo box that will show another dialog allowing entry of another country name. After the user enters the country name and clicks save, I would like the single static ObservableList to be updated with the new country and then it show up in the ComboBox. This part is not happening.
I'll show what DOES work, and what does not.
Saving a reference to the static list and updating that works. Like so:
public class CustomerController implements Initializable {
private ObservableList<Country> countryList;
#Override
public void initialize(URL url, ResourceBundle rb) {
countryList = Country.getCountryList();
comboCountry.setItems(countryList);
}
...
// Fired when clicking the "new country" button
#FXML
void handleNewCountry(ActionEvent event) {
Country country = new Country();
country.setCountry("Austria");
countryList.add(country);
}
}
This is what I would like to do, however it does not work:
public class CustomerController implements Initializable {
#FXML
private ComboBox<Country> comboCountry;
#Override
public void initialize(URL url, ResourceBundle rb) {
comboCountry.setItems(Country.getCountryList());
}
#FXML
void handleNewCountry(ActionEvent event) {
showScene("Country.fxml", "dialog.newCountry");
}
private void showScene(String sceneResource, String titleResource) {
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(sceneResource),
resourceBundle
);
Scene scene = new Scene(loader.load());
getNewStage(resourceBundle.getString(titleResource), scene).showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
private Stage getNewStage(String title, Scene scene) {
Stage stage = new Stage();
stage.setTitle(title);
stage.setResizable(false);
stage.setScene(scene);
stage.initOwner(rootPane.getScene().getWindow());
stage.initModality(Modality.APPLICATION_MODAL);
return stage;
}
}
The Country class:
public class Country extends BaseModel {
private int countryID;
private StringProperty country;
private static ObservableList<Country> countryList; // The static observable list
public Country() {
countryList = FXCollections.observableArrayList();
country = new SimpleStringProperty();
}
public int getCountryID() {
return countryID;
}
public void setCountryID(int countryID) {
this.countryID = countryID;
}
public StringProperty countryProperty() {
return this.country;
}
public String getCountry() {
return this.country.get();
}
public void setCountry(String country) {
this.country.set(country);
}
public boolean equals(Country country) {
if (this.getCountry().compareToIgnoreCase(country.getCountry()) != 0) {
return false;
}
return true;
}
public static ObservableList<Country> getCountryList() {
if (countryList.size() < 1) {
updateCountryList();
}
return countryList;
}
public static void updateCountryList() {
countryList.clear();
ArrayList<Country> daoList = CountryDao.listCountries();
for (Country country : daoList) {
countryList.add(country);
}
}
#Override
public String toString() {
return this.getCountry();
}
}
And the dialog for entering a new country:
public class CountryController implements Initializable {
#FXML
private TextField textCountry;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
void handleSave(ActionEvent event) {
Country country = new Country();
country.setCountry(textCountry.getText().trim());
CountryDao.insert(country); // Insert the country into the database
Country.updateCountryList(); // Update the static ObservableList
close();
}
#FXML
void handleCancel() {
close();
}
void close() {
final Stage stage = (Stage) textCountry.getScene().getWindow();
stage.close();
}
}
So, my theory is that somehow the ComboBox is creating a new instance of the ObservableList when setItems is called. I'm really not sure though. A static object should only have one instance, so updating it from anywhere should update that ComboBox. Anyone know what's up with this?
You're creating a new ObservableList instance every time the Country constructor is invoked. This way a list different to the one used with the ComboBox is modified.
If you really need to keep the list of countries in a static field (this is considered bad practice), you should make sure to only create a single ObservableList:
private static final ObservableList<Country> countryList = FXCollections.observableArrayList();
(Remove the assignment of this field from the constructor too.)

ComboBoxTableCell, Callback

I have a question that I can not solve in any way. I've read everything I've found on the web, and tried dozens of codes, but I still have no solution.
It is a JavaFX project that follows the MVC model. The fxml file is the following:
The main class:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
AnchorPane root = null;
try {
root = (AnchorPane) FXMLLoader.load(getClass().getResource("sample.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
The controller for the fxml file is:
public class Controller {
#FXML
private ResourceBundle resources;
#FXML
private URL location;
#FXML
private TableView<Fila> taula;
#FXML
private TableColumn<Fila, String> c1;
#FXML
private TableColumn<Fila, ObservableList<String>> c2;
#FXML
void initialize() {
assert taula != null : "fx:id=\"taula\" was not injected: check your FXML file 'sample.fxml'.";
assert c1 != null : "fx:id=\"c1\" was not injected: check your FXML file 'sample.fxml'.";
assert c2 != null : "fx:id=\"c2\" was not injected: check your FXML file 'sample.fxml'.";
ObservableList<Fila> dades = FXCollections.observableArrayList(
new Fila("First", FXCollections.observableArrayList("1", "2", "3")),
new Fila("Second", FXCollections.observableArrayList("4", "5", "6")),
new Fila("Third", FXCollections.observableArrayList("7", "8", "9"))
);
c1.setCellValueFactory(cellData -> cellData.getValue().c1Property());
c2.setCellValueFactory(cellData -> cellData.getValue().c2Property());
taula.setItems(dades);
}
}
And the bean class is:
public class Fila {
private StringProperty c1;
private ListProperty<String> c2;
public Fila(String c1, ObservableList<String> c2) {
this.c1 = new SimpleStringProperty(c1);
this.c2 = new SimpleListProperty<>(c2);
}
public String getC1() {
return c1.get();
}
public StringProperty c1Property() {
return c1;
}
public void setC1(String c1) {
this.c1.set(c1);
}
public ObservableList<String> getC2() {
return c2.get();
}
public ListProperty<String> c2Property() {
return c2;
}
public void setC2(ObservableList<String> c2) {
this.c2.set(c2);
}
}
What I do not get is that in the second column a ComboBox appears, with the values ​​that now appear as List ...
I think it could be achieved using the setCellFactory method together with a Callback for column where we want the ComboBox, but I am unable to get it ...
Sorry for my level of English; It's obvious that I'm using a translator ...
You can do
c2.setCellFactory(column -> new TableCell<Fila, ObservableList<String>>() {
private final ComboBox<String> combo = new ComboBox<>();
{
combo.valueProperty().addListener((obs, oldValue, newValue) -> {
// in real life, update model appropriately here...
System.out.println("Selected "+newValue+" for "+getTableView().getItems().get(getIndex()).getC1());
});
}
#Override
protected void updateItem(ObservableList<String> items, boolean empty) {
super.updateItem(items, empty);
if (empty) {
setGraphic(null);
} else {
combo.setItems(items);
// in real life, do combo.setValue(...) with some value from model
setGraphic(combo);
}
}
});
Your model doesn't really supply enough information for a combo box, as it doesn't have any property representing which item in the combo box is selected. Presumably you just didn't add this in the example you posted; the listener on the combo box's valueProperty() should update this appropriately, and the updateItem() method should set the value of the combo box depending on the appropriate data.

JavaFX TableView Cell color change depending on text value

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.

Filling ChoiceBox with custom property in JavaFX

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();
}

Resources