After typing in the cell, I want the focus
go to the next edit cell automatically.
How to do this ?
I'm using
tv.getSelectionModel (). select (row + 1);
tv.edit (row + 1, name); // <--- here not work edition next line
But unfortunately it is not working within setOnEditCommit
When it's called by clicking a button that is outside the tableView for example it works. The impression I have is that after setOnEditCommit it puts the last line cell's over.
How to solve this?
Thank's
File of properties
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.image.ImageView;
public class Lin {
private SimpleStringProperty number;
private SimpleStringProperty name;
public Lin(String number, String name ) {
this.number = new SimpleStringProperty(number);
this.name = new SimpleStringProperty(name);
}
public SimpleStringProperty numberProperty() {
return number;
}
public String getNumber() {
return number.getValue();
}
public SimpleStringProperty nameProperty() {
return name;
}
public String getName() {
return name.getValue();
}
}
import javafx.application.Application;
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.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TableTesteEdit extends Application {
TableView tv = new TableView();
public ObservableList<Lin> data = FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage) {
TableColumn<Lin,String> number = new TableColumn("#");
TableColumn<Lin,String> name = new TableColumn("Name");
name.setPrefWidth(400);
number.setCellValueFactory(new PropertyValueFactory("number"));
name.setCellValueFactory(new PropertyValueFactory("name"));
name.setCellFactory( TextFieldTableCell.<Lin>forTableColumn() );
name.setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<Lin, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Lin, String> t) {
int row = t.getTablePosition().getRow();
((Lin)t.getTableView().getItems().get(row)).nameProperty().setValue(t.getNewValue());
tv.getSelectionModel().select(row+1);
tv.edit(row+1, name); // <--- here not work edition next line
}
}
);
tv.setEditable(true);
number.setEditable(false);
name.setEditable(true);
data.addAll( new Lin("1","AFTER EditCommit go next cell and edit auto"),
new Lin("2","AFTER EditCommit go next cell and edit auto"),
new Lin("3","Congratulations !!!")
);
tv.getColumns().addAll(number, name);
tv.setItems(data);
Scene scene = new Scene(tv, 300, 250);
primaryStage.setTitle("Edit Auto next Cell");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I think the baked-in behavior of the table's editing sets the editing cell to null once an edit has been committed. My guess is that this happens after the onEditCommit handler has been invoked, so your call to edit the next cell is basically revoked.
A (not very satisfactory) way to fix this is to wrap the call to tv.edit(...) in Platform.runLater(...), which has the effect of delegating it to a runnable which is executed after the pending events have been handled:
// tv.edit(row+1, name);
Platform.runLater(() -> tv.edit(row+1, name));
Caveat: As commented below, this method is not reliable, so if this is mission-critical you should not use this approach. If you're just looking for something that is convenient for the user, if not fully reliable, then this may suffice. Unless you're willing to rewrite the table view skin, this may be as good as it gets...
You should look into the .requestFocus() method if there is something in the next cell that can pull the focus to it for example if you can only type in 4 letters you could use setOnAction(event->{ if(checkLength()) cell2.requestFocus()});
Good Luck
Related
I have a table view that shows the time sheets of employees working hours on different projects, from Monday to Friday. The data source is a list of time sheet classes with float properties to each day of the week. I would like to, for instance, show a value that adds all the hours from the Monday column and show a Monday Hours total. I thought it would be easy to do with a little research because, to me, wanting a column total would be common in data driven applications that would use TableViews but I can't seem to find an elegant answer, or really any answer at all. It would be easier if I could just calculate a column total whenever the list of timesheets changed, but I also have to listen for when the column values change/are edited by the user. Any ideas?
Here is a non-tornadofx version. In setOnEditCommit, update the observable list and sum the column.
Main
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class App extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
Label total = new Label("0");
ObservableList<Time> data = FXCollections.observableArrayList();
data.add(new Time(0));
data.add(new Time(3));
TableView<Time> tableView = new TableView();
tableView.setEditable(true);
tableView.setItems(data);
total.setText(Integer.toString(data.stream().mapToInt(Time::getHours).sum()));
TableColumn<Time, Integer> column1 = new TableColumn("Time");
column1.setCellValueFactory(new PropertyValueFactory<>("hours"));
column1.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Integer>()
{
#Override
public String toString(Integer t)
{
return Integer.toString(t);
}
#Override
public Integer fromString(String string)
{
return Integer.parseInt(string);
}
}));
column1.setOnEditCommit((CellEditEvent<Time, Integer> t) -> {
((Time) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setHours(t.getNewValue());
total.setText(Integer.toString(data.stream().mapToInt(Time::getHours).sum()));
});;
tableView.getColumns().add(column1);
VBox vbox = new VBox(tableView, total);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Time
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Time
{
private final IntegerProperty hours = new SimpleIntegerProperty();
public Time(int hours)
{
this.hours.set(hours);
}
public IntegerProperty hoursProperty()
{
return hours;
}
public void setHours(int hours)
{
this.hours.set(hours);
}
public Integer getHours()
{
return this.hours.getValue();
}
}
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?
I would like to apply two or three different styles to text in a single cell in a TableView.
For example, I'd like the single cell to have text formatted like this:
Edge of the Sun [EP] (Disc 2)
I'd really like to do it with colors, as well.
I know how to apply styling to the entire cell, but I don't even know where to start for applying style to part of the text.
Putting the data in different columns isn't a viable option.
Below's a quick example that
uses TextFlow to style parts of a text
implements a custom TableCell that has a TextFlow as its graphics and updates the text parts as appropriate
Note that there is a slight visual glitch: the prefHeight of the flow seems to return the accumulated height of the lines as if they were wrapped even if they aren't, thus making the row height oversized. As a quick hack, the computePrefHeight is overridden to force a single line - with the drawback that the other line/s simply disappear if the column width is decreased. Pretty sure there's something better but too lazy to further dig ;)
import java.util.Locale;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
/**
*
*/
public class TableFormattedCell extends Application {
public static class MyCell extends TableCell<Locale, Locale> {
private TextFlow flow;
private Label displayName;
private Label displayLanguage;
public MyCell() {
displayName = new Label();
displayName.setStyle("-fx-font-weight: bold");
displayLanguage = new Label();
displayLanguage.setStyle("-fx-font-style: italic; -fx-text-fill: darkviolet");
flow = new TextFlow(displayName, displayLanguage) {
#Override
protected double computePrefHeight(double width) {
// quick hack to force into single line ...
// there must be something better ..
return super.computePrefHeight(-1);
}
};
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(flow);
}
#Override
protected void updateItem(Locale item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
displayName.setText("");
displayLanguage.setText("");
} else {
displayName.setText(item.getDisplayName() + " ");
displayLanguage.setText(item.getDisplayLanguage());
}
}
}
private Parent getContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(
Locale.getAvailableLocales()));
TableColumn<Locale, String> countryCode = new TableColumn<>("CountryCode");
countryCode.setCellValueFactory(new PropertyValueFactory<>("country"));
TableColumn<Locale, String> language = new TableColumn<>("Language");
language.setCellValueFactory(new PropertyValueFactory<>("language"));
table.getColumns().addAll(countryCode, language);
TableColumn<Locale, Locale> local = new TableColumn<>("Locale");
local.setCellValueFactory(c -> new SimpleObjectProperty<>(c.getValue()));
local.setCellFactory(e -> new MyCell());
table.getColumns().addAll(local);
BorderPane pane = new BorderPane(table);
return pane;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent(), 800, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableFormattedCell.class.getName());
}
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;
}
}
}
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