How to bind multiple properties to one observable in JavaFX [closed] - javafx

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Update 3: solution
I finally got it: just save a reference of MyRoot in a property:
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
private MyRoot root;
#Override
public void start(Stage primaryStage) {
root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
Update 3 solution end
Update 2:
So, I'm still having problems with losing my bindings and it's due to garbage collection; that I can reproduce. See the follwing Minimal Working Example.
The structure of the example is somewhat special, that is because I use the MVP framework from Adam Bien: afterburner.fx. And I wanted to simulate the same structure in my example.
I read a lot (i.e. here, here) and understand the basic problem but still can't really grasp it.
I'm somewhat new to JavaFX, hope somebody can give me a hint.
Reproduce:
When you click on an item in the table, the content of the label changes due to the binding between the selected model in the table (Select) and the label (Show). MyRoot manages both.
When you click on the button garbage collection is executed and the binding is gone.
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage primaryStage) {
MyRoot root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
MyRoot
package my.group.javafxtestbinding;
import javafx.scene.layout.HBox;
public class MyRoot {
public HBox myRoot;
private final Select select;
private final Show show;
public MyRoot() {
select = new Select();
show = new Show();
myRoot = new HBox();
myRoot.getChildren().addAll(select.myTable, show.myLabel);
show.selectedModelProperty().bind(select.selectedModelProperty());
}
}
Select
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Select {
private ObjectProperty<MyModel> selectedModel;
public TableView<MyModel> myTable;
private final TableColumn<MyModel, String> myColumn;
public Select() {
this.selectedModel = new SimpleObjectProperty<>();
myTable = new TableView();
myColumn = new TableColumn("Name");
myTable.getColumns().add(myColumn);
myTable.setItems(getList());
myColumn.setCellValueFactory(cellData -> cellData.getValue().getNameProperty());
ChangeListener<MyModel> changeListener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
selectedModel.set(newValue);
}
};
myTable.getSelectionModel().selectedItemProperty().addListener(changeListener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
private ObservableList<MyModel> getList() {
MyModel model1 = new MyModel("Joe");
MyModel model2 = new MyModel("Jim");
MyModel model3 = new MyModel("Jack");
ObservableList<MyModel> list = FXCollections.observableArrayList(model1, model2, model3);
return list;
}
}
Show
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
public class Show {
private ObjectProperty<MyModel> selectedModel;
public Label myLabel;
public Show() {
this.selectedModel = new SimpleObjectProperty<>();
myLabel = new Label("Init");
ChangeListener listener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
if (newValue != null) {
myLabel.setText(newValue.getName());
}
}
};
this.selectedModel.addListener(listener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
}
MyModel
package my.group.javafxtestbinding;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class MyModel {
private final StringProperty name;
public MyModel(String name) {
this.name = new SimpleStringProperty(name);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty getNameProperty() {
return name;
}
}
Update 2 end
Update 1:
Ok, thank you for your comments.
I thought I was doing it wrong. But if you say that this is the correct way then the problem must be somewhere else. I will post it when I find the problem...
Update 1 end
Original Post:
I have problems binding many properties to one observable.
Let's say I have three (or more) properties ObjectProperty<Foo> firstProp in FirstObject, ObjectProperty<Foo> secondProp in SecondObject and ObjectProperty<Foo> thirdProp in ThirdObject.
How can I bind second and third property to the first which is the observable.
So when firstProp changes the ChangeListeners attached to secondProp and thirdProp will be fired.
What I tried:
SecondObject.secondProperty().bind(FirstObject.firstProperty());
ThirdObject.thirdProperty().bind(FirstObject.firstProperty());
The result is that it sometimes works and sometimes not. If I add more bindings in the same way they do not work at all.
Is this not working because of this:
Note that JavaFX has all the bind calls implemented through weak listeners. This means the bound property can be garbage collected and stopped from being updated.
Javadoc bind()
I find a lot of answers to how to bind many observables to one property, but not the other way.
Can somebody point me in the right direction?

Related

TestFX with JUnit4: How can I press the ENTER key in a certain TextField?

I want to test, if a certain TextField (maybe there are several TextFields) has an EventHandler set via setOnAction. In the test code I can set the content (e.g. "HelloWorld") into the TextField. In my understanding I have to place the curser at the end of the text in the TextField and then, call press(KeyCode.ENTER). Is there a TestFX call to place the curser a a certain point within a certain TextField? Or is there another way to test this?
Regards, Jörg
[EDIT]
Here is an example:
package testestfx;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class SimpleGui extends VBox {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(SimpleGui.class);
private TextField field1;
private TextField field2;
private Label label;
public SimpleGui() {
field1 = new TextField("Textfield1");
field2 = new TextField("Textfield2");
field1.setOnAction((event) -> onActionDoThis());
field2.setOnAction((event) -> onActionDoThat());
label = new Label("Label");
getChildren().addAll(field1, field2 );
}
public TextField getField1() {
return field1;
}
public TextField getField2() {
return field2;
}
public Label getLabel() {
return label;
}
void onActionDoThis() {
logger.info("This");
label.setText("This");
}
void onActionDoThat() {
logger.info("That");
label.setText("That");
}
}
And here is the test:
package testtestfx;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
import org.junit.Test;
import org.testfx.framework.junit.ApplicationTest;
import testestfx.SimpleGui;
import static org.junit.Assert.assertEquals;
public class TestSimpleGUI extends ApplicationTest {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(TestSimpleGUI.class);
private SimpleGui gui;
#Override
public void start(final Stage stage) throws Exception {
super.start(stage);
gui = new SimpleGui();
stage.setScene(new Scene(gui));
stage.show();
}
#Test
public void testEnterKeystroke() {
TextField t2 = gui.getField2();
TextField t1 = gui.getField1();
Label l1 = gui.getLabel();
t2.setText("bar");
t1.setText("foo");
press(KeyCode.ENTER);
assertEquals("That", l1.getText());
}
}
The console output is:
15:43:29.366 [JavaFX Application Thread] [INFO] SimpleGui - This
org.junit.ComparisonFailure:
Expected :That
Actual :This
```
You can set the focus on a TextField like this:
myTextField.requestFocus();
kind regards
Amadeus

JavaFX ComboBox check input and change it

I am trying to build a Combobox in JavaFX that should work as followed.
The user should only type numbers in, but the typed numbers have to be formattet.
ex. 111-111-1111.
So if the user types in three numbers a - should be added automatically.
I figured out how to do that.
I blocked everything but numbers with a TexFormatter.
Now the main Part of the problem comes the part after this.
I added a keyevent.key_released place the caret at the end when a key is released.
But if the user is typing too fast it won't work.
Most of the time it works just fine like that, but when is the user every doing something only the way I expect?
I could not find another way to get the actual value of the combobox, because it seems to refresh after hitting enter or so.
Adding the - in the Textformatter resultet in the programm listening to itself and I wasn't able to place the caret at the end position.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author
*/
public class tester extends Application{
ComboBox<String> combo = new ComboBox<String>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println("test");
HBox root = new HBox();
//
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change t) {
System.out.println(t.getText());
if (t.isReplaced())
if(t.getText().matches("[^0-9]")) {
t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));}
if (t.isAdded()) {
// Add in Formatter //
if (t.getControlText().length() == 2 || t.getControlText().length() == 6 && t.getText().matches("[0-9]")){
t.setText(t.getText() + "-");
combo.getEditor().end();
}
//
if (t.getText().matches("[^0-9]")) {
t.setText("");}
}
return t;
}
};
combo.setEditable(true);
combo.getEditor().setTextFormatter(new TextFormatter<>(filter));
combo.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
combo.getEditor().end();
});
root.getChildren().add(combo);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}

JavaFX TableView and ObservableList - How to auto-update the table?

I know questions similar to this have been asked, and on different dates, but I'll put an SSCCE in here and try to ask this simply.
I would like to be able to update the data model, and have any views upon it automatically update, such that any caller updating the model is not aware of whatever views there presently are. This is what I learned/tried so far, and without calling TableView.refresh() it does not update. What am I missing?
main.java:
package application;
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage stage) {
// data
ObservableList<Crew> data = FXCollections.observableArrayList();
data.addAll(new Crew(1, "A"), new Crew(2, "B"));
// table
TableColumn<Crew, Integer> crewIdCol = new TableColumn<Crew, Integer>("Crew ID");
crewIdCol.setCellValueFactory(new PropertyValueFactory<Crew, Integer>("crewId"));
crewIdCol.setMinWidth(120);
TableColumn<Crew, String> crewNameCol = new TableColumn<Crew, String>("Crew Name");
crewNameCol.setCellValueFactory(new PropertyValueFactory<Crew, String>("crewName"));
crewNameCol.setMinWidth(180);
TableView<Crew> table = new TableView<Crew>(data);
table.getColumns().addAll(crewIdCol, crewNameCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// button
Button button = new Button(" test ");
button.setOnAction(ae -> {
// test
StringProperty nameProp = data.get(0).crewName();
if(nameProp.get().equals("A")) {
data.get(0).setCrewName("foo");
// table.refresh();
System.out.println("foo");
} else {
data.get(0).setCrewName("A");
// table.refresh();
System.out.println("A");
}
});
VBox box = new VBox(10);
box.setAlignment(Pos.CENTER);;
box.getChildren().addAll(table, button);
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Crew.java
package application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Crew {
private final IntegerProperty crewId = new SimpleIntegerProperty();
private final StringProperty crewName = new SimpleStringProperty();
Crew(int id, String name) {
crewId.set(id);
crewName.set(name);
}
public IntegerProperty crewId() { return crewId; }
public final int getCrewId() { return crewId.get(); }
public final void setCrewId(int id) { crewId.set(id); }
public StringProperty crewName() { return crewName; }
public final String getCrewName() { return crewName.get(); }
public final void setCrewName(String name) { crewName.set(name); }
}
Your model class Crew has the "wrong" name for the property accessor methods. Without following the recommended method naming scheme, the (somewhat legacy code) PropertyValueFactory will not be able to find the properties, and thus will not be able to observe them for changes:
package application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Crew {
private final IntegerProperty crewId = new SimpleIntegerProperty();
private final StringProperty crewName = new SimpleStringProperty();
Crew(int id, String name) {
crewId.set(id);
crewName.set(name);
}
public IntegerProperty crewIdProperty() { return crewId; }
public final int getCrewId() { return crewId.get(); }
public final void setCrewId(int id) { crewId.set(id); }
public StringProperty crewNameProperty() { return crewName; }
public final String getCrewName() { return crewName.get(); }
public final void setCrewName(String name) { crewName.set(name); }
}
Alternatively, just implement the callback directly:
crewIdCol.setCellValueFactory(cellData -> cellData.getValue().crewIdProperty());
in which case the compiler will ensure that you use an existing method name for the property.

Understanding CheckBoxTableCell changelistener using setSelectedStateCallback

I'm trying to follow: CheckBoxTableCell changelistener not working
The given code answer to that question is below and dependent on the model 'Trainee'
final CheckBoxTableCell<Trainee, Boolean> ctCell = new CheckBoxTableCell<>();
ctCell.setSelectedStateCallback(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer index) {
return table.getItems().get(index).selectedProperty();
}
});
I would like to obtain that selected property value and add a listener to it, but I don't think I'm doing it right. I attempted to add all kind of listeners to it so that I know when the checkbox in each row is changed and I can add logic to each. I presume the code above allow ctCell to now observe changes and I can just call a change listener to it and detect selection per given row.
I tried some change properties here just to detect the changes:
ctCell.selectedStateCallbackProperty().addListener(change -> {
System.out.println("1Change happened in selected state property");
});
ctCell.selectedProperty().addListener(change -> {
System.out.println("2Change happened in selected property");
});
ctCell.itemProperty().addListener(change -> {
System.out.println("3Change happened in item property");
});
ctCell.indexProperty().addListener(change -> {
System.out.println("4Change happened in index property");
});
...but none seemed to be called.
This is the shorten set up that I have:
requestedFaxCol.setCellValueFactory(new PropertyValueFactory("clientHasRequestedFax"));
requestedFaxCol.setCellFactory(CheckBoxTableCell.forTableColumn(requestedFaxCol));
final CheckBoxTableCell<ClinicClientInfo, Boolean> ctCell = new CheckBoxTableCell<>();
ctCell.setSelectedStateCallback(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer index) {
return clinicLinkTable.getItems().get(index).clientHasRequestedFaxProperty();}
});
Let me know if I need to provide a more information! What am I not understanding in terms of why I cannot bridge a change listener to my table cell check boxes? Or if someone can point out the a direction for me to try. Thanks!
UPDATE to depict the ultimate goal of this question
package testapp;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TestApp extends Application {
private TableView<ClinicClientInfo> clientTable = new TableView<>();
private TableColumn<ClinicClientInfo, String> faxCol = new TableColumn<>("Fax");
private TableColumn<ClinicClientInfo, Boolean> requestedFaxCol = new TableColumn<>("Requested Fax");
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
ObservableList<ClinicClientInfo> list = FXCollections.observableArrayList(
new ClinicClientInfo("", false),
new ClinicClientInfo("945-342-4324", true));
root.getChildren().add(clientTable);
clientTable.getColumns().addAll(faxCol, requestedFaxCol);
clientTable.setItems(list);
clientTable.setEditable(true);
clientTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
faxCol.setCellValueFactory(new PropertyValueFactory<>("clinicFax"));
faxCol.setVisible(true);
requestedFaxCol.setCellValueFactory(new PropertyValueFactory("clientHasRequestedFax"));
requestedFaxCol.setCellFactory(CheckBoxTableCell.forTableColumn(requestedFaxCol));
requestedFaxCol.setVisible(true);
requestedFaxCol.setEditable(true);
//My attempt to connect the listener
//If user selects checkbox and the fax value is empty, the alert should prompt
CheckBoxTableCell<ClinicClientInfo, Boolean> ctCell = new CheckBoxTableCell<>();
ctCell.setSelectedStateCallback(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer index) {
ObservableValue<Boolean> itemBoolean = clientTable.getItems().get(index).clientHasRequestedFaxProperty();
itemBoolean.addListener(change -> {
ClinicClientInfo item = clientTable.getItems().get(index);
if(item.getClinicFax().isEmpty() && item.getClientHasRequestedFax()){
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Warning");
alert.show();
}
});
return itemBoolean;
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public class ClinicClientInfo {
private final StringProperty clinicFax;
private final BooleanProperty clientHasRequestedFax;
public ClinicClientInfo(String fax, boolean clientHasRequestedFax){
this.clinicFax = new SimpleStringProperty(fax);
this.clientHasRequestedFax = new SimpleBooleanProperty(clientHasRequestedFax);
}
public String getClinicFax(){
return clinicFax.get();
}
public void setClinicFax(String clinicFax){
this.clinicFax.set(clinicFax);
}
public StringProperty clinicFaxProperty(){
return clinicFax;
}
public boolean getClientHasRequestedFax(){
return clientHasRequestedFax.get();
}
public void setClientHasRequestedFax(boolean clientHasRequestedFax){
this.clientHasRequestedFax.set(clientHasRequestedFax);
}
public BooleanProperty clientHasRequestedFaxProperty(){
return clientHasRequestedFax;
}
}
}
The goal is to get a prompt when the user tries to select fax request when the fax string is empty.
This is already fully explained in the question you already linked, so I don't know what more I can add here other than just to restate it.
The check boxes in the cell are bidirectionally bound to the property that is returned by the selectedStateCallback. If no selectedStateCallback is set, and the cell is attached to a column whose cellValueFactory returns a BooleanProperty (which covers almost all use cases), then the check box's state is bidirectionally bound to that property.
In your code sample, I don't understand what ctCell is for. You just create it, set a selectedStateCallBack on it, and then don't do anything with it. It has nothing to do with your table and nothing to do with the cell factory you set.
So in your case, no selected state callback is set on the cells produced by your cell factory, and the cell value factory returns a boolean property, so the default applies, and the check box state is bidirectionally bound to the property returned by the cell value factory. All you have to do is register a listener with those properties.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CheckBoxTableCellTestApp extends Application {
private TableView<ClinicClientInfo> clientTable = new TableView<>();
private TableColumn<ClinicClientInfo, String> faxCol = new TableColumn<>("Fax");
private TableColumn<ClinicClientInfo, Boolean> requestedFaxCol = new TableColumn<>("Requested Fax");
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
ObservableList<ClinicClientInfo> list = FXCollections.observableArrayList(
new ClinicClientInfo("", false),
new ClinicClientInfo("945-342-4324", true));
// add listeners to boolean properties:
for (ClinicClientInfo clinic : list) {
clinic.clientHasRequestedFaxProperty().addListener((obs, faxWasRequested, faxIsNowRequested) ->{
System.out.printf("%s changed fax request from %s to %s %n",
clinic.getClinicFax(), faxWasRequested, faxIsNowRequested);
});
}
root.getChildren().add(clientTable);
clientTable.getColumns().addAll(faxCol, requestedFaxCol);
clientTable.setItems(list);
clientTable.setEditable(true);
clientTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
faxCol.setCellValueFactory(new PropertyValueFactory<>("clinicFax"));
faxCol.setVisible(true);
requestedFaxCol.setCellValueFactory(new PropertyValueFactory<>("clientHasRequestedFax"));
requestedFaxCol.setCellFactory(CheckBoxTableCell.forTableColumn(requestedFaxCol));
requestedFaxCol.setVisible(true);
requestedFaxCol.setEditable(true);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public class ClinicClientInfo {
private final StringProperty clinicFax;
private final BooleanProperty clientHasRequestedFax;
public ClinicClientInfo(String fax, boolean clientHasRequestedFax){
this.clinicFax = new SimpleStringProperty(fax);
this.clientHasRequestedFax = new SimpleBooleanProperty(clientHasRequestedFax);
}
public String getClinicFax(){
return clinicFax.get();
}
public void setClinicFax(String clinicFax){
this.clinicFax.set(clinicFax);
}
public StringProperty clinicFaxProperty(){
return clinicFax;
}
public boolean getClientHasRequestedFax(){
return clientHasRequestedFax.get();
}
public void setClientHasRequestedFax(boolean clientHasRequestedFax){
this.clientHasRequestedFax.set(clientHasRequestedFax);
}
public BooleanProperty clientHasRequestedFaxProperty(){
return clientHasRequestedFax;
}
}
}

Populate TableView with ObservableMap JavaFX

I wanted to know if it is possible to use a ObservableMap to populate a TableView ?
I use ObservableMap instead of ObservableList because I need to add and delete often, so I need to minimize the cost.
My hashMap use an BigInteger as key field and a type with many properties as value field.
In my tableView I just want to display the values with a column per properties. I hope that is clear
Thanks
I've been trying to do this. I guess the post is old but I don't see any answers anywhere on the net. The examples use the map key for columns and then a list of maps for every row. I'd like to see the rows as keys and their associated values. It's a long example.
package tablemap;
import static java.lang.Math.random;
import java.util.Map;
import java.util.TreeMap;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableMap extends Application {
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
Map<String,LineItem> mapData = new TreeMap<>();
for (int i = 0; i < 3; i++)
mapData.put(String.valueOf(random()), new LineItem(String.valueOf(i),"i"));
ObservableList<Map.Entry<String,LineItem>> listData =
FXCollections.observableArrayList(mapData.entrySet());
TableView<Map.Entry<String,LineItem>> tv = new TableView(listData);
TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
keyCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getKey()));
TableColumn<Map.Entry<String,LineItem>,String> lineNoCol = new TableColumn("Line No");
lineNoCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getValue().getLineNo()));
TableColumn<Map.Entry<String,LineItem>,String> descCol = new TableColumn("Desc");
descCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getValue().getDesc()));
descCol.setCellFactory(TextFieldTableCell.forTableColumn());
descCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
t.getTableView().getItems().get(t.getTablePosition().getRow())
.getValue().setDesc(t.getNewValue());
});
tv.getColumns().addAll(keyCol,lineNoCol, descCol);
tv.setEditable(true);
tv.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Button btnOut = new Button("out");
btnOut.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
for (Map.Entry<String,LineItem> me : mapData.entrySet()){
System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
}
for (Map.Entry<String,LineItem> me : listData){
System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
}
}
});
root.getChildren().addAll(tv,btnOut);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("Map Table Test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
And the LineItem Class Code
package tablemap;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/* LineItem class */
public class LineItem {
private final StringProperty lineNo = new SimpleStringProperty();
private final StringProperty desc = new SimpleStringProperty();
public LineItem(String ln, String dsc) {
lineNo.set(ln); desc.set(dsc);
}
public String getLineNo() {return (lineNo.getValue() != null) ?lineNo.get():"";}
public void setLineNo(String lineNo) {this.lineNo.set(lineNo);}
public StringProperty lineNoProperty() {return lineNo;}
public String getDesc() {return (desc.getValue() != null) ?desc.get():"";}
public void setDesc(String desc) {this.desc.set(desc);}
public StringProperty descProperty() {return desc;}
public String toCSVString(){
return lineNo.getValueSafe()+","+
desc.getValueSafe()+"\n";
}
}
You can see after editing data and clicking out that changes in the list are reflected in the map. I still have to check the other way and handle insertions and deletions but that shouldn't be to hard.
I packaged up my Map Table listeners in a subclass of TableView.
package tablemap;
import java.util.AbstractMap;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.scene.control.TableView;
public class MapTableView<K,V> extends TableView<Map.Entry<K,V>>{
private final ObservableList<Map.Entry<K,V>> obsList;
private final ObservableMap<K,V> map;
private final MapChangeListener<K,V> mapChange;
private final ListChangeListener<Map.Entry<K,V>> listChange;
public MapTableView(ObservableMap<K,V> map) {
this.map = map;
obsList = FXCollections.observableArrayList(map.entrySet());
setItems(obsList);
mapChange = new MapChangeListener<K, V>() {
#Override
public void onChanged(MapChangeListener.Change<? extends K, ? extends V> change) {
obsList.removeListener(listChange);
if (change.wasAdded())
obsList.add(new AbstractMap.SimpleEntry(change.getKey(),change.getValueAdded()));
if (change.wasRemoved()){
//obsList.remove(new AbstractMap.SimpleEntry(change.getKey(),change.getValueRemoved()));
// ^ doesn't work always, use loop instead
for (Map.Entry<K,V> me : obsList){
if (me.getKey().equals(change.getKey())){
obsList.remove(me);
break;
}
}
}
obsList.addListener(listChange);
}
};
listChange = (ListChangeListener.Change<? extends Map.Entry<K, V>> change) -> {
map.removeListener(mapChange);
while (change.next()){
//maybe check for uniqueness here
if (change.wasAdded()) for (Map.Entry<K, V> me: change.getAddedSubList())
map.put(me.getKey(),me.getValue());
if (change.wasRemoved()) for (Map.Entry<K, V> me: change.getRemoved())
map.remove(me.getKey());
}
map.addListener(mapChange);
};
map.addListener(mapChange);
obsList.addListener(listChange);
}
//adding to list should be unique
public void addUnique(K key, V value){
boolean isFound = false;
//if a duplicate key just change the value
for (Map.Entry<K,V> me : getItems()){
if (me.getKey().equals(key)){
isFound = true;
me.setValue(value);
break;//only first match
}
}
if (!isFound) // add new entry
getItems().add(new AbstractMap.SimpleEntry<>(key,value));
}
//for doing lenghty map operations
public void removeMapListener(){
map.removeListener(mapChange);
}
//for resyncing list to map after many changes
public void resetMapListener(){
obsList.removeListener(listChange);
obsList.clear();
obsList.addAll(map.entrySet());
obsList.addListener(listChange);
map.addListener(mapChange);
}
}
It seems to work so far. I create with the following code :
final ObservableMap<String, LineItem> obsMap = FXCollections.observableHashMap();
final MapTableView<String,LineItem> mtv = new MapTableView(obsMap);
You can even edit the keys.
final TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
keyCol.setCellValueFactory(
(TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) ->
new SimpleStringProperty(p.getValue().getKey()));
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
final String oldKey = t.getOldValue();
final LineItem oldLineItem = obsMap.get(oldKey);
obsMap.remove(oldKey);//should remove from list but maybe doesn't always
obsMap.put(t.getNewValue(),oldLineItem);
});
You can see I added a method to remove and re add the map listeners. To add and remove 100k entries takes .65 secs w/out listeners and 5.2 secs with them.
Here's the whole thing in one file on pastebin. http://pastebin.com/NmdTURFt

Resources