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

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

Related

JavaFX TableView: Show sorted items + Background Task

I'm working with Javafx and I'm struggling to get a sorted table in place when using background Tasks to update. In the code here, which can be run standalone, I update a table in background.
What I'd like to do is that this table gets updated, and stays sorted in chronological order, so older train times appear at the top, and later ones at the bottom. The example produces times that are the opposite order on purpose, to see if sorting works.
I run some tests before I added concurrent update to the table, and the way I would do it is by calling:
private final ObservableList<StationBoardLine> data = FXCollections.observableArrayList(
new StationBoardLine("RE", "17:14", "Basel Bad Bf", "Basel SBB", "+3", "RE 5343"));
SortedList<StationBoardLine> sorted = new SortedList<>(data, new DelayComparator());
table.setItems(sorted);
However, now I'm not setting the items, but using the background task together with ReadOnlyObjectProperty and ReadOnlyObjectWrapper to append to it.
So, my question is, how can I make sure that as items are added, the list remains ordered? I've tried to see if I could reorder the list inside the call to Platform.runLater but didn't seem to work.
The link between the task updating the table and the table is set is here:
table.itemsProperty().bind(task.partialResultsProperty());
Thanks for help,
Galder
The way I would do it is by updating an ObservableList by the background task and use it as a "source" to create a SortedList. This SortedList would then act as the source of "items" to the TableView.
A general structure would be :
public class MyClass {
private TableView<T> tableView = new TableView;
private ObservableList<T> sourceList = FXCollections.observableArrayList();
public MyClass() {
...
SortedList<T> sortedList = new SortedList<>(sourceList, new MyComparator());
tableView.setItems(sortedList);
...
new Task<Void> {
protected Void call() {
... // Some background data fetch
Platform.runLater(() -> sourceList.add(data));
return null;
}
};
}
}
For your scenario, I would go with something that you already have. Therefore, instead of creating a new ObservableList to be used as a source for your SortedList, I would use the list returned by Task#getPartialResults().
The DelayComparator uses the value of the delay to compare and show the data in the TableView.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class App extends Application {
private TableView<StationBoardLine> table = new TableView<>();
private final ExecutorService exec = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 800, 600);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setEditable(true);
TableColumn typeCol = getTableCol("Type", 10, "type");
TableColumn departureCol = getTableCol("Departure", 30, "departure");
TableColumn stationCol = getTableCol("Station", 200, "station");
TableColumn destinationCol = getTableCol("Destination", 200, "destination");
TableColumn delayCol = getTableCol("Delay", 20, "delay");
TableColumn trainName = getTableCol("Train Name", 50, "trainName");
table.getColumns().addAll(
typeCol, departureCol, stationCol, destinationCol, delayCol, trainName);
root.setCenter(table);
PartialResultsTask task = new PartialResultsTask();
SortedList<StationBoardLine> sorted = new SortedList<>(task.getPartialResults(), new DelayComparator());
table.setItems(sorted);
exec.submit(task);
stage.setTitle("Swiss Transport Delays Board");
stage.setScene(scene);
stage.show();
}
private TableColumn getTableCol(String colName, int minWidth, String fieldName) {
TableColumn<StationBoardLine, String> typeCol = new TableColumn<>(colName);
typeCol.setMinWidth(minWidth);
typeCol.setCellValueFactory(new PropertyValueFactory<>(fieldName));
return typeCol;
}
static final class DelayComparator implements Comparator<StationBoardLine> {
#Override
public int compare(StationBoardLine o1, StationBoardLine o2) {
return o1.getDelay().compareTo(o2.getDelay());
}
}
public class PartialResultsTask extends Task<Void> {
private ObservableList<StationBoardLine>partialResults = FXCollections.observableArrayList();
public final ObservableList<StationBoardLine> getPartialResults() {
return partialResults;
}
#Override protected Void call() throws Exception {
System.out.println("Creating station board entries...");
for (int i=5; i >= 1; i--) {
Thread.sleep(1000);
if (isCancelled()) break;
StationBoardLine l = new StationBoardLine(
"ICE", "16:" + i, "Basel Bad Bf", "Chur", String.valueOf(i), "ICE 75");
Platform.runLater(() -> partialResults.add(l));
}
return null;
}
}
public static final class StationBoardLine {
private final SimpleStringProperty type;
private final SimpleStringProperty departure;
private final SimpleStringProperty station;
private final SimpleStringProperty destination;
private final SimpleStringProperty delay;
private final SimpleStringProperty trainName;
StationBoardLine(String type,
String departure,
String station,
String destination,
String delay,
String trainName) {
this.type = new SimpleStringProperty(type);
this.departure = new SimpleStringProperty(departure);
this.station = new SimpleStringProperty(station);
this.destination = new SimpleStringProperty(destination);
this.delay = new SimpleStringProperty(delay);
this.trainName = new SimpleStringProperty(trainName);
}
public String getType() {
return type.get();
}
public SimpleStringProperty typeProperty() {
return type;
}
public void setType(String type) {
this.type.set(type);
}
public String getDeparture() {
return departure.get();
}
public SimpleStringProperty departureProperty() {
return departure;
}
public void setDeparture(String departure) {
this.departure.set(departure);
}
public String getStation() {
return station.get();
}
public SimpleStringProperty stationProperty() {
return station;
}
public void setStation(String station) {
this.station.set(station);
}
public String getDestination() {
return destination.get();
}
public SimpleStringProperty destinationProperty() {
return destination;
}
public void setDestination(String destination) {
this.destination.set(destination);
}
public String getDelay() {
return delay.get();
}
public SimpleStringProperty delayProperty() {
return delay;
}
public void setDelay(String delay) {
this.delay.set(delay);
}
public String getTrainName() {
return trainName.get();
}
public SimpleStringProperty trainNameProperty() {
return trainName;
}
public void setTrainName(String trainName) {
this.trainName.set(trainName);
}
}
}

javafx : get the name of the column

How do I get the name of the column of a textfield inside a javaFX table?
I need this to check the value of the cells only in the "text2" column. I tried it with textfield.parent() but I didn't get a useful result.Edit: I just removed some unnessary log, which was not helpful for understanding.Now it is more convenient.
Here is my Code:
import java.util.ArrayList;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextArea;
import javafx.util.Callback;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/*interface inside_table
{
public String get_column_name
}*/
public class Supermain extends Application {
#Override
public void start(Stage primaryStage) {
ArrayList myindizes=new ArrayList();
final TableView<myTextRow> table = new TableView<>();
table.setEditable(true);
table.setStyle("-fx-text-wrap: true;");
//Table columns
TableColumn<myTextRow, String> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(new PropertyValueFactory<>("ID"));
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory());
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory());
//Add data
final ObservableList<myTextRow> data = FXCollections.observableArrayList(
new myTextRow(5, "Lorem","bla"),
new myTextRow(2, "Ipsum","bla")
);
table.getColumns().addAll(clmID, clmtext,clmtext2);
table.setItems(data);
HBox hBox = new HBox();
hBox.setSpacing(5.0);
hBox.setPadding(new Insets(5, 5, 5, 5));
Button btn = new Button();
btn.setText("Get Data");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (myTextRow data1 : data) {
System.out.println("data:" + data1.getText2());
}
}
});
hBox.getChildren().add(btn);
BorderPane pane = new BorderPane();
pane.setTop(hBox);
pane.setCenter(table);
primaryStage.setScene(new Scene(pane, 640, 480));
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell();
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
private String last_text;
public TextFieldCell() {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
last_text="";
this.setGraphic(textField);
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
//only if textfield is in the text2 column
if(isNowFocused){last_text=textField.getText(); System.out.println("NOW focus "+last_text);}
if (! isNowFocused && ! isValid(textField.getText())) {
textField.setText(last_text);
textField.selectAll();
System.out.println("blur");
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// myindizes.add(getIndex());
// Retrieve the actual String Property that should be bound to the TextField
// If the TextField is currently bound to a different StringProperty
// Unbind the old property and rebind to the new one
ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
SimpleStringProperty sp = (SimpleStringProperty) ov;
if (this.boundToCurrently == null) {
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(sp);
} else if (this.boundToCurrently != sp) {
this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(this.boundToCurrently);
}
double height = real_lines_height(textField.getText(), this.getWidth(), 30, 22);
textField.setPrefHeight(height);
textField.setMaxHeight(height);
textField.setMaxHeight(Double.MAX_VALUE);
// if height bigger than the biggest height in the row
//-> change all heights of the row(textfields ()typeof textarea) to this height
// else leave the height as it is
//System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
//this.textField.setText(item); // No longer need this!!!
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}//update
private boolean isValid(String s){
if(s.length()<7){return true;}
return false;
}
}
}
public class myTextRow {
private final SimpleIntegerProperty ID;
private final SimpleStringProperty text;
private final SimpleStringProperty text2;
public myTextRow(int ID, String text,String text2) {
this.ID = new SimpleIntegerProperty(ID);
this.text = new SimpleStringProperty(text);
this.text2 = new SimpleStringProperty(text2);
}
//setter
public void setID(int id) {
this.ID.set(id);
}
public void setText(String text) {
this.text.set(text);
}
public void setText2(String text) {
this.text2.set(text);
}
//getter
public int getID() {
return ID.get();
}
public String getText() {
return text.get();
}
public String getText2() {
return text2.get();
}
//properties
public StringProperty textProperty() {
return text;
}
public StringProperty text2Property() {
return text2;
}
public IntegerProperty IDProperty() {
return ID;
}
}
private static double real_lines_height(String s, double width, double heightCorrector, double widthCorrector) {
HBox h = new HBox();
Label l = new Label("Text");
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
double line_height = l.prefHeight(-1);
int new_lines = s.replaceAll("[^\r\n|\r|\n]", "").length();
// System.out.println("new lines= "+new_lines);
String[] lines = s.split("\r\n|\r|\n");
// System.out.println("line count func= "+ lines.length);
int count = 0;
//double rest=0;
for (int i = 0; i < lines.length; i++) {
double text_width = get_text_width(lines[i]);
double plus_lines = Math.ceil(text_width / (width - widthCorrector));
if (plus_lines > 1) {
count += plus_lines;
//rest+= (text_width / (width-widthCorrector)) - plus_lines;
} else {
count += 1;
}
}
//count+=(int) Math.ceil(rest);
count += new_lines - lines.length;
return count * line_height + heightCorrector;
}
private static double get_text_width(String s) {
HBox h = new HBox();
Label l = new Label(s);
l.setWrapText(false);
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
return l.prefWidth(-1);
}
}
There are probably (way) better ways to organize this, but probably the cleanest fix is just to define a boolean validate parameter to the constructor of your cell implementation. (You really don't want the logic to be "if the title of the column is equal to some specific text, then validate". You would be utterly screwed when your boss came in to the office and asked you to internationalize the application, or even just change the title of the column, for example.)
Using an entire inner class just to implement the callback seems completely redundant, but keeping that you would have to pass the parameter through it:
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
private final boolean validate ;
public TextFieldCellFactory(boolean validate) {
this.validate = validate ;
}
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell(validate);
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
private String last_text;
public TextFieldCell(boolean validate) {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
last_text="";
this.setGraphic(textField);
if (validate) {
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
//only if textfield is in the text2 column
if(isNowFocused){last_text=textField.getText(); System.out.println("NOW focus "+last_text);}
if (! isNowFocused && ! isValid(textField.getText())) {
textField.setText(last_text);
textField.selectAll();
System.out.println("blur");
}
});
}
}
// ...
}
Then of course you just do
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory(false));
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory(true));
(To properly answer your question, you can get the text of the column from within the cell to which it is attached with getTableColumn().getText(), but as I pointed out, actually basing the logic on the value displayed in a column header will make your code completely unmaintainable.)
And I guess for completeness, I should also mention that your TextFieldCellFactory class looks like it is not really serving any purpose. I would remove it entirely and just have the TextFieldCell class, and do
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(c -> new TextFieldCell(false));
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(c -> new TextFieldCell(true));

JAVAFX 8 ComboBox and ObservableList

I need a combobox populated through observablelist which contains specific data retrieved from DB. This is my source.
Model
public ObservableList<Bank> listBank = FXCollections.observableArrayList();
public static class Bank {
private final StringProperty id;
private final StringProperty name;
private Bank(
String id,
String name
) {
this.id = new SimpleStringProperty(id);
this.name = new SimpleStringProperty(name);
}
public StringProperty idProperty() { return id; }
public StringProperty nameProperty() { return name; }
}
View
#FXML
private ComboBox comboBank<Bank>;
public final void getBankDataFields() {
comboBank.setItems(model.listBank);
}
comboBank.setButtonCell(new ListCell<Bank>() {
#Override
protected void updateItem(Bank t, boolean bln) {
super.updateItem(t, bln);
if (t != null) {
setText(t.nameProperty().getValue().toUpperCase());
} else {
setText(null);
}
}
});
comboBank.setCellFactory(new Callback<ListView<Bank>, ListCell<Bank>>() {
#Override
public ListCell<Bank> call(ListView<Bank> p) {
return new ListCell<Bank>() {
#Override
protected void updateItem(Bank t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t.nomeProperty().getValue().toUpperCase());
} else {
setText(null);
}
}
};
}
});
comboBank.valueProperty().addListener((ObservableValue<? extends Bank> observable, Bank oldValue, Bank newValue) -> {
setIdBank(newValue.idProperty().getValue());
});
ComboBox is populated with NAME field and listener is used to get relative ID and pass it to a query for storing data on DB.
Ok, everything seems to work but i have two questions:
When user need to modify this record, i need to get the ID from DB and select the relative NAME in ComboBox. How can i do that?
comboBank.setValue(????);
Is there a better way to achieve this goal? An ObservableMap may substitute the ObservableList?
Thanks in advance.
There is an easier way to what you are trying to achieve. You should use a StringConverter on the ComboBox to display the names for the Bank instances.
comboBox.setConverter(new StringConverter<Bank>() {
#Override
public String toString(Bank object) {
return object.nameProperty().get();
}
#Override
public Bank fromString(String string) {
// Somehow pass id and return bank instance
// If not important, just return null
return null;
}
});
To get selected value i.e. instance of the selected bank, just use :
comboBox.getValue();
MCVE
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.util.stream.Collectors;
public class Main extends Application {
#Override
public void start(Stage stage) {
ComboBox<Bank> comboBox = new ComboBox<>();
ObservableList<Bank> items = FXCollections.observableArrayList(
new Bank("1", "A"), new Bank("2", "B"),
new Bank("3", "C"), new Bank("4", "D"));
comboBox.setItems(items);
StringConverter<Bank> converter = new StringConverter<Bank>() {
#Override
public String toString(Bank bank) {
return bank.nameProperty().get();
}
#Override
public Bank fromString(String id) {
return items.stream()
.filter(item -> item.idProperty().get().equals(id))
.collect(Collectors.toList()).get(0);
}
};
comboBox.setConverter(converter);
// Print the name of the Bank that is selected
comboBox.getSelectionModel().selectedItemProperty().addListener((o, ol, nw) -> {
System.out.println(comboBox.getValue().nameProperty().get());
});
// Wait for 3 seconds and select the item with id = 2
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(3));
pauseTransition.setOnFinished(event -> comboBox.getSelectionModel().select(converter.fromString("2")));
pauseTransition.play();
VBox root = new VBox(comboBox);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
stage.setScene(scene);
stage.show();
}
}

Javafx 8 Tableview selection with checkbox

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.)

Get ListCell via ListView

I have a ListView with my own ListCell<MyObject> implementation. Via a network signal, I receive an index of my ListCell that should be changed.
Over listView.getItems().get(index); there is no problem to access the model, but I want to make a layout change to the listCell with the received index and a layout change to the ListCell with the index+1;
How can I access the ListCell via the ListView?
I search for a method like this:
listView.getListCell(index);
Unfortunately right now there is no API to get List Cell by index or to get All children's(listcells) for ListView. One solution can be, define a new StringProperty specialIndicator in your MyObject class.
class MyObject {
....//u r properties
private StringProperty specialIndicator;
When ever you get index from network signal set this specialIndicator property of object and do forcerefresh of ListView
public void onReceivedNetWorkSignalIndex() {
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
//force refresh listview (it will trigger cellFactory again so that you can manipulate layout)
listView.setItems(null);
listView.setItems(allObjects);
}
As you already have custom Object ListView , i am assuming you already have custom cellFactory (if not you have to create one ) ,Modify your custom cell factory to handle this special Indicators
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Here is the whole sample Application ,you can look into it (in case the above explanation is not clear).
public class ListViewTest extends Application {
#Override
public void start(Stage stage) throws Exception {
VBox root = new VBox();
final ObservableList<MyObject> allObjects = FXCollections.observableArrayList(new MyObject("object0"), new MyObject("object1"),new MyObject("object2"),new MyObject("object3"),new MyObject("object4"));
final ListView<MyObject> listView = new ListView<>(allObjects);
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
setText("I am selected Index from Service");
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
setText("I am selected Index +1 from Service");
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Button serviceIndex2 = new Button("ServiceIndex2");
serviceIndex2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
int indexFromService =2;
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
listView.setItems(null);
listView.setItems(allObjects);
}
});
root.getChildren().addAll(listView,serviceIndex2);
Scene scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyObject {
private StringProperty name;
private StringProperty specialIndicator;
MyObject(String name) {
this.name = new SimpleStringProperty(name);
this.specialIndicator = new SimpleStringProperty();
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getSpecialIndicator() {
return specialIndicator.get();
}
public StringProperty specialIndicatorProperty() {
return specialIndicator;
}
public void setSpecialIndicator(String specialIndicator) {
this.specialIndicator.set(specialIndicator);
}
}
}
Here's a relatively simple approach, where there is just one "selected" index. Here I create a property to hold the index that is selected, and the cell factory just observes it, along with the cell's item property and index property, and sets the text via a binding. You could do something similar to set the graphic, if needed.
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<>();
for (int i=1; i<=20; i++) {
listView.getItems().add("Item "+i);
}
final HBox controls = new HBox(5);
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
final IntegerProperty selectionIndex = new SimpleIntegerProperty();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
selectionIndex.set(Integer.parseInt(indexField.getText()));
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> lv) {
final ListCell<String> cell = new ListCell<>();
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
switch (cell.getIndex() - selectionIndex.get()) {
case -1: return cell.getItem() + " (selected item below)";
case 0: return cell.getItem() + " (selected)";
case 1: return cell.getItem() + " (selected item above)";
default: return cell.getItem();
}
}
}
}, cell.itemProperty(), cell.indexProperty(), selectionIndex));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And here's a slightly more complex version. Here I have a custom data type which includes a boolean property. The update sets the boolean property of the specified item to true. The cell factory creates a cell, and observes the selected property both of the current item and of the previous item. Then, as before, it uses a binding to update the text of the cell.
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<MyDataType> listView = new ListView<>();
for (int i=0; i<=20; i++) {
listView.getItems().add(new MyDataType("Item "+i, false));
}
final HBox controls = new HBox(5);
controls.setPadding(new Insets(5));
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
int index = Integer.parseInt(indexField.getText());
if (index >= 0 && index < listView.getItems().size()) {
final MyDataType item = listView.getItems().get(index);
item.setSelected( ! item.isSelected() );
}
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<MyDataType>, ListCell<MyDataType>>() {
#Override
public ListCell<MyDataType> call(ListView<MyDataType> lv) {
final ListCell<MyDataType> cell = new ListCell<>();
final IntegerBinding previousIndex = cell.indexProperty().subtract(1);
final ObjectBinding<MyDataType> previousItem = Bindings.valueAt(listView.getItems(), previousIndex);
final BooleanBinding previousItemSelected = Bindings.selectBoolean(previousItem, "selected");
final StringBinding thisItemName = Bindings.selectString(cell.itemProperty(), "name");
final BooleanBinding thisItemSelected = Bindings.selectBoolean(cell.itemProperty(), "selected");
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
String value = cell.getItem().getName();
if (thisItemSelected.get()) {
value = value + " (selected) " ;
} else if (previousItemSelected.get()) {
value = value + " (selected item is above)";
}
return value ;
}
}
}, thisItemName, thisItemSelected, previousItemSelected));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MyDataType {
private final BooleanProperty selected ;
private final StringProperty name ;
public MyDataType(String name, boolean selected) {
this.name = new SimpleStringProperty(this, "name", name);
this.selected = new SimpleBooleanProperty(this, "selected", selected);
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public final StringProperty nameProperty() {
return name ;
}
public final boolean isSelected() {
return selected.get();
}
public final void setSelected(boolean selected) {
this.selected.set(selected);
}
public final BooleanProperty selectedProperty() {
return selected;
}
}
public static void main(String[] args) {
launch(args);
}
}
Cell has a style class called ".cell"
public Cell getListCell(ListView list, int index){
Object[]cells = list.lookupAll(".cell").toArray();
return (Cell)cells[index];
}
This is the method I used to solve the same problem. Please note that getting the cell view is considered bad practice, and shouldn't be done in a normal context, updating cells should only be done through the model, my special case was that I wanted to fire an event manually as part of a workaround.
private ListCell<?> getListCell(ListView<?> listView, int cellIndex) {
if (cellIndex == -1) {
return null;
}
//Virtual Flow is the container of all list cells
//Each ListView has exactly one VirtualFlow which we are searching for
Optional<VirtualFlow> virtualFlowOptional = listView.getChildrenUnmodifiable()
.stream()
.filter(node -> node instanceof VirtualFlow)
.map(n -> (VirtualFlow) n)
.findFirst();
if (virtualFlowOptional.isEmpty()) {
return null;
}
VirtualFlow<ListCell<?>> virtualFlow = virtualFlowOptional.get();
return virtualFlow.getCell(cellIndex);
}

Resources