JavaFX TableView Context menu get the selected ID - javafx

I have a TableView where I display data from my database. The TableView itself is not hard-coded on a specific table but it adds the columns and the data instead. I added Context menu, so when the user right clicks on an item, it deletes it. It works fine but I don't know how to get the ID which is the first column. Note that I don't want the selected index but the ID from the tableView (first column).
The questioned snippet:
removeMenuItem.setOnAction(event -> {
System.out.println(row.getItem()); // HERE
this.tableView.getItems().remove(row.getItem());
});
Basically, row.getItem() returns the record:
[2, Name, Stuff, Test Test, Category]
The question is how do I get the ID (in this case 2) or the name?
Full code:
package controllers;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.Callback;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TableController {
#FXML
private Label descriptionLabel;
#FXML
private TableView tableView;
public void setTableResultSet(String label, ResultSet resultSet) throws SQLException {
// Create context menu
createContextMenu();
// Set label text
descriptionLabel.setText(label);
// Resolve columns
ObservableList<ObservableList> data = resolveTableColumns(resultSet);
// Add records
addTableRecords(resultSet, data);
}
/**
* Create a context menu responsible for removing items.
*/
private void createContextMenu() {
tableView.setRowFactory(tableView -> {
final TableRow row = new TableRow();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem removeMenuItem = new MenuItem("Remove");
removeMenuItem.setOnAction(event -> {
System.out.println(row.getItem()); // HERE
this.tableView.getItems().remove(row.getItem());
});
contextMenu.getItems().add(removeMenuItem);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu)
);
return row;
});
}
/**
* Resolves table columns based on a ResultSet.
*
* #param resultSet the ResultSet.
* #return
* #throws SQLException
*/
private ObservableList<ObservableList> resolveTableColumns(ResultSet resultSet) throws SQLException {
ObservableList<ObservableList> data = FXCollections.observableArrayList();
for (int i = 0; i < resultSet.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn col = new TableColumn(resultSet.getMetaData().getColumnName(i + 1));
col.setCellValueFactory((Callback<TableColumn.CellDataFeatures<ObservableList, String>, ObservableValue<String>>) param -> {
return new SimpleStringProperty(param.getValue().get(j).toString());
});
tableView.getColumns().add(col);
}
return data;
}
/**
* Adds table data from a ResultSet.
*
* #param resultSet the ResultSet.
* #param data the data.
* #throws SQLException
*/
private void addTableRecords(ResultSet resultSet, ObservableList<ObservableList> data) throws SQLException {
while (resultSet.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
row.add(resultSet.getString(i));
}
data.add(row);
}
tableView.setItems(data);
}
}
Edit based on #James_D's comment:
package controllers;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.Callback;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TableController {
#FXML
private Label descriptionLabel;
#FXML
private TableView<ObservableList<String>> tableView;
public void setTableResultSet(String label, ResultSet resultSet) throws SQLException {
// Create context menu
createContextMenu();
// Set label text
descriptionLabel.setText(label);
// Resolve columns
ObservableList<ObservableList<String>> data = resolveTableColumns(resultSet);
// Add records
addTableRecords(resultSet, data);
}
/**
* Create a context menu responsible for removing items.
*/
private void createContextMenu() {
tableView.setRowFactory(tableView -> {
final TableRow<ObservableList<String>> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem removeMenuItem = new MenuItem("Изтрий");
removeMenuItem.setOnAction(event -> {
System.out.println(row.getItem().get(1));
this.tableView.getItems().remove(row.getItem());
});
contextMenu.getItems().add(removeMenuItem);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu)
);
return row;
});
}
/**
* Resolves table columns based on a ResultSet.
*
* #param resultSet the ResultSet.
* #return
* #throws SQLException
*/
private ObservableList<ObservableList<String>> resolveTableColumns(ResultSet resultSet) throws SQLException {
ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for (int i = 0; i < resultSet.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn col = new TableColumn(resultSet.getMetaData().getColumnName(i + 1));
col.setCellValueFactory((Callback<TableColumn.CellDataFeatures<ObservableList, String>, ObservableValue<String>>) param -> {
return new SimpleStringProperty(param.getValue().get(j).toString());
});
tableView.getColumns().add(col);
}
return data;
}
/**
* Adds table data from a ResultSet.
*
* #param resultSet the ResultSet.
* #param data the data.
* #throws SQLException
*/
private void addTableRecords(ResultSet resultSet, ObservableList<ObservableList<String>> data) throws SQLException {
while (resultSet.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
row.add(resultSet.getString(i));
}
data.add(row);
}
tableView.setItems(data);
}
}

Don't use raw types: that way you can access the appropriate methods for the data you retrieve from the table (without have to downcast anywhere).
Specifically, you declare you represent your data as an
ObservableList<ObservableList>, which means that each row is represented as a (raw) ObservableList. Since, looking at your addTableRecords method, each element of a row is a String, the rows should be represented as ObservableList<String>, and the overall data as ObservableList<ObservableList<String>>.
The TableView and associated UI elements should also be parameterized: i.e. you should be using
TableView<ObservableList<String>>
TableColumn<ObservableList<String>, String>
TableRow<ObservableList<String>
If you make these changes, then in your createContextMenu() method you will have
TableRow<ObservableList<String>> row = new TableRow<>();
Consequently the return type of row.getItem() will be ObservableList<String> and you can call methods defined in ObservableList (or more generally, List) on the returned value. Specifically, calling get(...) will return the String in a given cell in the row:
String id = row.getItem().get(0);
String name = row.getItem().get(1);
etc.
Put all together, this looks like
package controllers;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.util.Callback;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TableController {
#FXML
private Label descriptionLabel;
#FXML
private TableView<ObservableList<String>> tableView;
public void setTableResultSet(String label, ResultSet resultSet) throws SQLException {
// Create context menu
createContextMenu();
// Set label text
descriptionLabel.setText(label);
// Resolve columns
ObservableList<ObservableList<String>> data = resolveTableColumns(resultSet);
// Add records
addTableRecords(resultSet, data);
}
/**
* Create a context menu responsible for removing items.
*/
private void createContextMenu() {
tableView.setRowFactory(tableView -> {
final TableRow<ObservableList<String>> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem removeMenuItem = new MenuItem("Изтрий");
removeMenuItem.setOnAction(event -> {
String id = row.getItem().get(0);
String name = row.getItem().get(1);
// do whatever you need with id and name, etc.
this.tableView.getItems().remove(row.getItem());
});
contextMenu.getItems().add(removeMenuItem);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu)
);
return row;
});
}
/**
* Resolves table columns based on a ResultSet.
*
* #param resultSet the ResultSet.
* #return
* #throws SQLException
*/
private ObservableList<ObservableList<String>> resolveTableColumns(ResultSet resultSet) throws SQLException {
ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for (int i = 0; i < resultSet.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn<ObservableList<String>, String> col = new TableColumn<>(resultSet.getMetaData().getColumnName(i + 1));
col.setCellValueFactory(param ->
new SimpleStringProperty(param.getValue().get(j))
);
tableView.getColumns().add(col);
}
return data;
}
/**
* Adds table data from a ResultSet.
*
* #param resultSet the ResultSet.
* #param data the data.
* #throws SQLException
*/
private void addTableRecords(ResultSet resultSet, ObservableList<ObservableList<String>> data) throws SQLException {
while (resultSet.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
row.add(resultSet.getString(i));
}
data.add(row);
}
tableView.setItems(data);
}
}

Related

JavaFX - bind properties of corresponding TableRows in different TableViews

I have two TableViews in the same scene that are closely related. I want to set up a listener such that when the user hovers a certain row in one table, the row with the same index in the other table is "hovered" as well.
I'm trying to solve this with a custom row factory tableView.setRowFactory(...). Inside the factory call(...) method I can toggle a CSS pseudo-class (.myclass:hover) on the target row, like:
row.hoverProperty().addListener((obs, o, n) -> {
myOtherTable.[get row here].pseudoClassStateChanged(PseudoClass.getPseudoClass("hover"), true);
});
As you can see in my factory method I have a reference to the second TableView object, myOtherTable. I guess I have to get hold of its TableRow objects to go ahead and set the pseudo class, but I can't figure out how.
Maybe is there a better way to do this?
Create a single property representing the index of the hovered row, and a PseudoClass:
IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");
Now create a row factory that creates table rows that observe this value and their own index:
Callback<TableView<T>, TableCell<T>> rowFactory = tv -> {
TableRow<T> row = new TableRow<T>() {
private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
() -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
hoveredRowIndex);
{
shouldAppearHovered.addListener(
(obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));
hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
if (isNowHovered) {
hoveredRowIndex.set(getIndex());
} else {
hoveredRowIndex.set(-1);
}
});
}
};
return row;
};
(Replace T with the actual type of the table.)
And now use the row factory for both tables. You can use the CSS selector
.table-row-cell:appear-hovered {
/* ... */
}
to style the rows that should appear to be hovered, or use
.table-row-cell:appear-hovered .table-cell {
/* ... */
}
to style individual cells in that row.
Here's a SSCCE:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ConnectedHoverTables extends Application {
private IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
private PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");
#Override
public void start(Stage primaryStage) {
HBox root = new HBox(10, createTable(), createTable());
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TableView<Item> createTable() {
TableView<Item> table = new TableView<>();
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow<Item>() {
private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
() -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
hoveredRowIndex);
{
shouldAppearHovered.addListener(
(obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));
hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
if (isNowHovered) {
hoveredRowIndex.set(getIndex());
} else {
hoveredRowIndex.set(-1);
}
});
}
};
return row;
});
table.setOnMouseClicked(e -> System.gc());
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
table.getItems().setAll(createData());
return table;
}
private List<Item> createData() {
Random rng = new Random();
List<Item> items = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
Item item = new Item("Item " + i, rng.nextInt(1000));
items.add(item);
}
return items;
}
private <S, T> TableColumn<S, T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
If I remember correctly, you can't access directly a row of a TableView. The only way to get the row index is to access the attribute indexProperty when you define the CellFactory.
I advise you to create rather a personalized extending TableRow or TableCell object where you can stock an id or something like that...

pagination of dynamodb using java in my project

I am trying to implement pagination of dynamodb using java in my project, i have implemented it, I am struggling in how to get the no. of elements in result per page using DynamoDBScanExpression. Can anyone help. Thanks
Here is my code
package com.morrisons.extendedrange.dao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList;
import com.amazonaws.services.dynamodbv2.datamodeling.QueryResultPage;
import com.amazonaws.services.dynamodbv2.datamodeling.ScanResultPage;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ReturnConsumedCapacity;
import com.google.inject.Inject;
import com.morrisons.extendedrange.config.ExtendedRangeServiceConfiguration;
import com.morrisons.extendedrange.entity.ExtendedRangeEntity;
import com.morrisons.extendedrange.exception.ErrorCodes;
import com.morrisons.extendedrange.exception.ExtendedRangeServiceException;
import com.morrisons.extendedrange.model.ExtendedRange;
import com.morrisons.extendedrange.util.Logger;
import com.morrisons.extendedrange.util.LoggerFactory;
/**
* The Class ExtendedRangeDao.
*/
public class ExtendedRangeDao {
private static Logger LOGGER;
#Inject
private LoggerFactory logFactory;
Map<String, AttributeValue> lastEvaluatedKey = null;
#Inject
private void init() {
LOGGER = logFactory.getLogger(ExtendedRangeDao.class);
}
/** The range service configuration. */
#Inject
private ExtendedRangeServiceConfiguration extendedRangeServiceConfiguration;
/**
* Gets the range.
*
* #param storeId
* the store id
* #param catalogId
* the catalog id
* #return the range
*/
public ExtendedRange getExtendedRange(String catalogId, String productId, String page) {
LOGGER.debug("ExtendedRangeDao : getExtendedRange start ");
List<ExtendedRange> extendedRangeList = new ArrayList<ExtendedRange>();
try {
AmazonDynamoDBClient client = extendedRangeServiceConfiguration.getDynamoDBClient();
DynamoDBMapper mapper = new DynamoDBMapper(client);
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
eav.put(":val1", new AttributeValue().withS(productId));
eav.put(":val2", new AttributeValue().withS(catalogId));
DynamoDBScanExpression scanExpression=new DynamoDBScanExpression()
.withFilterExpression("(productname =:val1 or productid =:val1) and catalogid = :val2 ")
.withExpressionAttributeValues(eav)
.withConsistentRead(true)
.withLimit(5)
.withExclusiveStartKey(lastEvaluatedKey) ;
ScanResultPage<ExtendedRangeEntity> queryResultPage = mapper.scanPage(ExtendedRangeEntity.class,
scanExpression, getDBMapperConfigForFetch());
List<ExtendedRangeEntity> extendedRangeEntityList = queryResultPage.getResults();
lastEvaluatedKey = queryResultPage.getLastEvaluatedKey();
if (extendedRangeEntityList.isEmpty()) {
String errorMessage = "No Record found or Multiple data found";
LOGGER.debug(errorMessage);
throw new ExtendedRangeServiceException(ErrorCodes.NO_RECORDS_FOUND, "ExtendedRangeEntity not found");
} else {
for (ExtendedRangeEntity extendedRangeEntity : extendedRangeEntityList) {
ExtendedRange extendedRange = new ExtendedRange();
copyProperties(extendedRange, extendedRangeEntity);
extendedRangeList.add(extendedRange);
LOGGER.debug("ExtendedRangeDao : getExtendedRange end ");
}
ExtendedRange returnExtendedRange = new ExtendedRange();
returnExtendedRange.setExtendedRangeList(extendedRangeList);
return returnExtendedRange;
}
} catch (AmazonServiceException e) {
String errorMessage = "Error in retrieving Data in DynamoDB";
LOGGER.error(errorMessage, e);
throw new ExtendedRangeServiceException(ErrorCodes.AMAZON_SERVICE_ERROR, errorMessage);
}
}
private DynamoDBMapperConfig getDBMapperConfigForFetch() {
String tableName = extendedRangeServiceConfiguration.getTableNameConfig()
.getTableName(ExtendedRangeEntity.class);
TableNameOverride tableNameOverride = new TableNameOverride(tableName);
DynamoDBMapperConfig dbMapperConfig = new DynamoDBMapperConfig(tableNameOverride);
return dbMapperConfig;
}
public ExtendedRangeServiceConfiguration getExtendedRangeServiceConfiguration() {
return extendedRangeServiceConfiguration;
}
public void setRangeServiceConfiguration(ExtendedRangeServiceConfiguration extendedRangeServiceConfiguration) {
this.extendedRangeServiceConfiguration = extendedRangeServiceConfiguration;
}
private void copyProperties(ExtendedRange target, ExtendedRangeEntity src) {
target.setProductId(src.getProductId());
target.setLeadTimeUOM(src.getLeadTimeUOM());
target.setCatalogId(src.getCatalogId());
target.setProductDesc(src.getProductDesc());
target.setProductName(src.getProductName());
target.setLeadTime(src.getLeadTime());
target.setCanBeOrderedFromDate(src.getCanBeOrderedFromDate());
target.setCanBeOrderedToDate(src.getCanBeOrderedFromDate());
}
}
The size() method should provide the number of elements in a page.
queryResultPage.getResults().size()

Changing table row color using a property that would not be visible in any column

I need to change the table row color using a property that would not be visible in any column of a tableview. I did the following:
create a model class Person (serialNumber, first, last).
create an observableList of Person using an extractor.
create two tableviews(tableview1, tableview2) and one listview that all sharing the same data.
tableview1 has a serialCol1 column with a visible property set to
false.
I want to change tableview1 row color using the serialNumber property that is bound to a column in a tableview2.
Here is the complete program:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
/**
*
* #author kachna
*/
public class Extractor extends Application {
private final TableView<Person> tableView1 = new TableView<>();
private final TableView<Person> tableView2 = new TableView<>();
private final ListView<Person> listView = new ListView<>();
//observable list with extractor
private final ObservableList<Person> data = FXCollections.observableArrayList(p -> new Observable[]{p.serialNumberProperty(), p.firstProperty(), p.lastProperty()});
static class Person {
final IntegerProperty serialNumber;
final StringProperty first;
final StringProperty last;
public Person(int serialNumber, String first, String last) {
this.first = new SimpleStringProperty(first);
this.last = new SimpleStringProperty(last);
this.serialNumber = new SimpleIntegerProperty(serialNumber);
}
public IntegerProperty serialNumberProperty() {
return serialNumber;
}
public StringProperty firstProperty() {
return first;
}
public StringProperty lastProperty() {
return last;
}
#Override
public String toString() {
return "Person{" + "first=" + first.get() + ", last=" + last.get() + '}';
}
}
#Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
VBox vBox = new VBox(10);
VBox.setVgrow(tableView2, Priority.ALWAYS);
root.setPadding(new Insets(10));
initTableViews();
initListView();
getData();
Label label1 = new Label("TableView 1");
label1.setStyle("-fx-font-size: 24px;\n"
+ "-fx-font-weight: bold;");
Label label2 = new Label("TableView 2");
label2.setStyle("-fx-font-size: 24px;\n"
+ "-fx-font-weight: bold;");
vBox.getChildren().addAll(label1, tableView1,label2, tableView2);
root.setCenter(vBox);
root.setRight(listView);
Scene scene = new Scene(root, 600, 400);
stage.setScene(scene);
stage.show();
}
private void initTableViews() {
// first table view
tableView1.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView1.setEditable(true);
tableView1.setRowFactory(tv -> new TableRow<Person>() {
#Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (item.serialNumber.get() % 2 == 0) {
setStyle("-fx-background-color: orange;");
} else {
setStyle(" ");
}
} else {
setStyle(" ");
}
}
});
TableColumn<Person, Number> serialCol1 = new TableColumn<>("Serial Number");
serialCol1.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
serialCol1.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {
#Override
public String toString(Number object) {
return object.toString();
}
#Override
public Number fromString(String string) {
return Integer.parseInt(string);
}
}));
// make the serialCol1 column invisible
serialCol1.setVisible(false);
TableColumn<Person, String> firstCol1 = new TableColumn<>("First Name");
firstCol1.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
firstCol1.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastCol1 = new TableColumn<>("Last Name");
lastCol1.setCellFactory(TextFieldTableCell.forTableColumn());
lastCol1.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
tableView1.getColumns().addAll(serialCol1, firstCol1, lastCol1);
tableView1.setItems(data);
// second table view
tableView2.setEditable(true);
tableView2.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<Person, Number> serialCol = new TableColumn<>("Serial Number");
serialCol.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
serialCol.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {
#Override
public String toString(Number object) {
return object.toString();
}
#Override
public Number fromString(String string) {
return Integer.parseInt(string);
}
}));
TableColumn<Person, String> firstCol2 = new TableColumn<>("First Name");
firstCol2.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
TableColumn<Person, String> lastCol2 = new TableColumn<>("Last Name");
lastCol2.setCellFactory(TextFieldTableCell.forTableColumn());
lastCol2.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
tableView2.getColumns().addAll(serialCol, firstCol2, lastCol2);
tableView2.setItems(data);
}
private void initListView() {
//list view
listView.setCellFactory(list -> new ListCell<Person>() {
#Override
protected void updateItem(Person value, boolean empty) {
super.updateItem(value, empty);
if (!empty && value != null) {
if (value.serialNumber.get() % 2 == 0) {
setStyle("-fx-background-color: orange;");
} else {
setStyle(" ");
}
setText(String.format("%s %s %s", value.serialNumber.get(), value.firstProperty().get(), value.lastProperty().get()));
} else {
setText(null);
setStyle(" ");
}
}
});
listView.setItems(data);
}
private void getData() {
data.setAll(IntStream.range(0, 10)
.mapToObj(i -> new Person(i, "first" + i, "last" + i))
.collect(Collectors.toList()));
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Problem:
the style of tableview1 doesn't change instantly after applying a
change. I have to scroll hrough the rows to see the style updated. the style of the listview is changed instantly without any problems.
The updateItem method is not bound to the property lifecycle of its item ( an item must not be an Observable ), but rather gets called by the View (ListView/TableView) whenever it deems it necessary to update the data representation. When you scroll a Row off screen it gets nulled ( I assume for performance reasons ) and updated again when in screen.
What you want to do is to bind the stylePropertyof the row to its items serialNumberPropertylike so:
tableView1.setRowFactory( tv -> new TableRow<Person>()
{
#Override
protected void updateItem( final Person item, final boolean empty )
{
super.updateItem( item, empty );
if ( !empty && item != null )
{
this.styleProperty().bind( Bindings.createStringBinding( () ->
{
if ( item.serialNumber.get() % 2 == 0 )
{
return "-fx-background-color: orange;";
}
return " ";
} , item.serialNumberProperty() ) );
}
else
{
/*
* As per comment in the Cell API
*/
setText( null );
setGraphic( null );
this.styleProperty().unbind();
setStyle( " " );
}
}
} );
I also recommend consulting the documentation of javafx.scene.control.Cell#updateitem(...) as it is marked as "Expert API".
Link to full example.

JavaFx Bindings : is there any way to bind value to observable List?

i have table view it's contents is observable list that contains numbers and i have a text field that should display the sum of these values in the table is there any way to bind this text fields to sum of the number properties .
note : the user may edit the values in this list , may add more elements , may delete some element how can i bind the sum of these numbers correctly using javafx binding instead of doing this by the old fashion way iterate over the list and sum the numbers manually and every change reiterate over it again .
An ObservableList will fire update events if (and only if) you create the list with an extractor. The extractor is a function that maps each element of the list to an array of Observables; if any of those Observables change their value, the list fires the appropriate update events and becomes invalid.
So the two steps here are:
Create the list with an extractor
Create a binding that computes the total whenever the list is invalidated.
So if you have a model class for your table such as:
public class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get();
}
public final void setValue(int value) {
valueProperty().set(value);
}
// other properties, etc...
}
Then you create the table with:
TableView<Item> table = new TableView<>();
table.setItems(FXCollections.observableArrayList(item ->
new Observable[] { item.valueProperty() }));
Now you can create the binding with
IntegerBinding total = Bindings.createIntegerBinding(() ->
table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
table.getItems());
Implementation note: the two arguments to createIntegerBinding above are a function that computes the int value, and any values to observe. If any of the observed values (here there is just one, table.getItems()) is invalidated, then the value is recomputed. Remember we created table.getItems() so it would be invalidated if any of the item's valueProperty()s changed. The function that is the first argument uses a lambda expression and the Java 8 Streams API, it is roughly equivalent to
() -> {
int totalValue = 0 ;
for (Item item : table.getItems()) {
totalValue = totalValue + item.getValue();
}
return totalValue ;
}
Finally, if you want a label to display the total, you can do something like
Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Here is an SSCCE:
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import javafx.util.converter.NumberStringConverter;
public class TotallingTableView extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(
column("Item", Item::nameProperty, new DefaultStringConverter()));
table.getColumns().add(
column("Value", Item::valueProperty, new NumberStringConverter()));
table.setItems(FXCollections.observableArrayList(
item -> new Observable[] {item.valueProperty() }));
IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Item("Item "+i, i))
.forEach(table.getItems()::add);
IntegerBinding total = Bindings.createIntegerBinding(() ->
table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
table.getItems());
Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Button add = new Button("Add item");
add.setOnAction(e ->
table.getItems().add(new Item("New Item", table.getItems().size() + 1)));
Button remove = new Button("Remove");
remove.disableProperty().bind(
Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
remove.setOnAction(e ->
table.getItems().remove(table.getSelectionModel().getSelectedItem()));
HBox buttons = new HBox(5, add, remove);
buttons.setAlignment(Pos.CENTER);
VBox controls = new VBox(5, totalLabel, buttons);
VBox.setVgrow(totalLabel, Priority.ALWAYS);
totalLabel.setMaxWidth(Double.MAX_VALUE);
totalLabel.setAlignment(Pos.CENTER_RIGHT);
BorderPane root = new BorderPane(table, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title,
Function<S, ObservableValue<T>> property, StringConverter<T> converter) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(TextFieldTableCell.forTableColumn(converter));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
Note that this is not the most efficient possible implementation, but the one that (IMHO) keeps the code the cleanest. If you have a very large number of items in the table, recomputing the total from scratch by iterating through and summing them all might be prohibitively expensive. The alternative approach is to listen for add/remove changes to the list. When an item is added, add its value to the total, and register a listener with the value property that updates the total if the value changes. When an item is removed from the list, remove the listener from the value property and subtract the value from the total. This avoids continually recomputing from scratch, but the code is harder to decipher.
I would just add a listener to the ObservableList and have it update the label any time there is a change.
list.addListener((obs, oldValue, newValue) -> {
textfield.setText(list.stream().mapToInt(x -> x.intValue()).sum();
});
Something like that should work

JavaFX tableview hiding a selected row

In my JavaFX application I am trying to hide the selected rows in the tableview. I used CSS as below.
.hideRow
{
-fx-cell-size: 0px;
-fx-border-width: 0;
}
I used the below row factory for the achieving the same.
import java.util.Collections;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.util.Callback;
/
**
* This is the table view row style factory class. * This class is responsible
* for performing style changes for enable,disable,hide and un hide of the rows.
*
* #param <T>
*/
public class StyleChangingRowFactory<T> implements
Callback<TableView<T>, TableRow<T>> {
private TableRow rowSelected;
private final String styleClass;
private final ObservableList<Integer> styledRowIndices;
private final Callback<TableView<T>, TableRow<T>> baseFactory;
/**
* Construct a <code>StyleChangingRowFactory</code>, specifying the name of
* the style class that will be applied to rows determined by
* <code>getStyledRowIndices</code> and a base factory to create the
* <code>TableRow</code>. If <code>baseFactory</code> is <code>null</code>,
* default table rows will be created.
*
* #param styleClass The name of the style class that will be applied to
* specified rows.
* #param baseFactory A factory for creating the rows. If null, default
* <code>TableRow<T></code>s will be created using the default
* <code>TableRow</code> constructor.
*/
public StyleChangingRowFactory(String styleClass, Callback<TableView<T>, TableRow<T>> baseFactory) {
this.styleClass = styleClass;
this.baseFactory = baseFactory;
this.styledRowIndices = FXCollections.observableArrayList();
}
/**
* Construct a <code>StyleChangingRowFactory</code>, which applies
* <code>styleClass</code> to the rows determined by
* <code>getStyledRowIndices</code>, and using default
* <code>TableRow</code>s.
*
* #param styleClass
*/
public StyleChangingRowFactory(String styleClass) {
this(styleClass, null);
}
#Override
public TableRow<T> call(final TableView<T> tableView) {
final TableRow<T> row;
if (baseFactory == null) {
row = new TableRow<>();
} else {
row = baseFactory.call(tableView);
}
row.setOnDragEntered(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent t) {
tableView.getSelectionModel().clearSelection();
tableView.getSelectionModel().selectRange(rowSelected.getIndex(), row.getIndex());
}
});
row.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
rowSelected = row;
Dragboard db = row.getTableView().startDragAndDrop(TransferMode.LINK);
ClipboardContent content = new ClipboardContent();
content.put(DataFormat.PLAIN_TEXT, "XData");
db.setContent(content);
tableView.getSelectionModel().clearSelection();
t.consume();
}
});
row.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
rowSelected = row;
final int index = row.getIndex();
if (event.getButton() == MouseButton.SECONDARY && index >= 0 && !tableView.getSelectionModel().isSelected(index)) {
tableView.getSelectionModel().clearSelection();
}
}
});
row.indexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs,
Number oldValue, Number newValue) {
updateStyleClass(row);
}
});
styledRowIndices.addListener(new ListChangeListener<Integer>() {
#Override
public void onChanged(Change<? extends Integer> change) {
updateStyleClass(row);
tableView.getColumns().get(0).setVisible(false);
tableView.getColumns().get(0).setVisible(true);
}
});
return row;
}
/**
*
* #return The list of indices of the rows to which <code>styleClass</code>
* will be applied. Changes to the content of this list will result in the
* style class being immediately updated on rows whose indices are either
* added to or removed from this list.
*/
public ObservableList<Integer> getStyledRowIndices() {
return styledRowIndices;
}
private void updateStyleClass(TableRow<T> row) {
final ObservableList<String> rowStyleClasses = row.getStyleClass();
if (styledRowIndices.contains(row.getIndex())) {
if (!rowStyleClasses.contains(styleClass)) {
rowStyleClasses.add(styleClass);
}
} else {
// remove all occurrences of styleClass:
rowStyleClasses.removeAll(Collections.singletonList(styleClass));
}
}
}
I declared the row factory as
StyleChangingRowFactory rowFactory = new StyleChangingRowFactory<>("hideRow");
I have two pop up menus in my table, they are hide and unhide.
In my controller I used the below methods with implementation for those menus.
private void hidePressed() {
rowFactory.getHiddenRowIndices().addAll(cfiTableView.getSelectionModel().getSelectedIndices());
}
private void unHidePressed() {
rowFactory.getHiddenRowIndices().removeAll(cfiTableView.getSelectionModel().getSelectedIndices());
}
The problem is when I hide the selected row(s). It is also hiding some other rows in the table view. Please help me to solve this issue.

Resources