整个列表的分页 TableView 排序错误
Error with Paginated TableView sort for whole list
我正在尝试实现一个允许按 JavaFX 中的所有项目排序的分页 TableView。我从这里实现了分页 table 视图:。由 jewelsea 和 tim buthe 提供。
我在想,因为 table 视图只访问项目的子列表,所以我想根据我对关于 Java 文档排序的部分:https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableView.html#setItems-javafx.collections.ObservableList-
// bind the sortedList comparator to the TableView comparator
//i m guessing it extends the sorting from the table to the actual list?
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
然后为相同的子列表索引刷新 table视图(由于整个列表已排序,现在应该对其进行排序)。
基本上,我想使用 table 列比较器对完整列表进行排序,然后使用新的排序列表“刷新”table 视图。这可行吗?或者有更简单的方法来解决这个问题吗?
我还参考了其他参考资料 material,例如:https://incepttechnologies.blogspot.com/p/javafx-tableview-with-pagination-and.html 但我发现它很难理解,因为到处都是含糊不清的解释。
快速提取我的 TouchDisplayEmulatorController 中的核心组件 class
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.List;
public class TouchDisplayEmulatorController extends Application {
public TableView sensorsTable;
public List<Sensor> sensors;
public int rowsPerPage = 14;
public GridPane grids = new GridPane();
public long timenow;
public void start(final Stage stage) throws Exception {
grids = new GridPane();
setGridPane();
Scene scene = new Scene(grids, 1024, 768);
stage.setScene(scene);
stage.setTitle("Table pager");
stage.show();
}
//public static void main(String[] args) throws Exception {
// launch(args);
//}
public void setGridPane(){
processSensors();
sensorsGrid();
}
public void sensorsGrid(){
buildTable();
int numOfPages = 1;
if (sensors.size() % rowsPerPage == 0) {
numOfPages = sensors.size() / rowsPerPage;
} else if (sensors.size() > rowsPerPage) {
numOfPages = sensors.size() / rowsPerPage + 1;
}
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(this::createPage);
pagination.setMaxPageIndicatorCount(numOfPages);
grids.add(pagination, 0, 0);
}
private Node createPage(int pageIndex) {
int fromIndex = pageIndex * rowsPerPage;
int toIndex = Math.min(fromIndex + rowsPerPage, sensors.size());
sensorsTable.setItems(FXCollections.observableArrayList(sensors.subList(fromIndex, toIndex)));
return new BorderPane(sensorsTable);
}
public void processSensors(){
sensors = new ArrayList<>();
// long timenow = OffsetDateTime.now(ZoneOffset.UTC).toInstant().toEpochMilli()/1000;
// StringTokenizer hildetoken = new StringTokenizer(msg);
for (int i=0; i<20; i++) {
sensors.add(new Sensor(String.valueOf(i), "rid-"+i, "sid-"+i, "0", "0", "no condition"));
}
}
public void buildTable() {
sensorsTable = new TableView();
TableColumn<Sensor, String> userid = new TableColumn<>("userid");
userid.setCellValueFactory(param -> param.getValue().userid);
userid.setPrefWidth(100);
TableColumn<Sensor, String> resourceid = new TableColumn<>("resourceid");
resourceid.setCellValueFactory(param -> param.getValue().resourceid);
resourceid.setPrefWidth(100);
TableColumn<Sensor, String> column1 = new TableColumn<>("sid");
column1.setCellValueFactory(param -> param.getValue().sid);
column1.setPrefWidth(100);
TableColumn<Sensor, String> column2 = new TableColumn<>("timestamp");
column2.setCellValueFactory(param -> param.getValue().timestamp);
column2.setPrefWidth(100);
TableColumn<Sensor, String> column3 = new TableColumn<>("reading");
column3.setCellValueFactory(param -> param.getValue().reading);
column3.setPrefWidth(100);
TableColumn<Sensor, String> column4 = new TableColumn<>("last contacted");
column4.setCellFactory(new Callback<TableColumn<Sensor, String>, TableCell<Sensor, String>>() {
@Override
public TableCell<Sensor, String> call(TableColumn<Sensor, String> sensorStringTableColumn) {
return new TableCell<Sensor, String>() {
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
this.setTextFill(Color.WHITE);
if (item.contains("@")) {
this.setTextFill(Color.BLUEVIOLET);
} else if (item.equals("> 8 hour ago")) {
this.setStyle("-fx-background-color: red;");
} else if (item.equals("< 8 hour ago")) {
this.setStyle("-fx-background-color: orange;");
//this.setTextFill(Color.ORANGE);
} else if (item.equals("< 4 hour ago")) {
this.setStyle("-fx-background-color: yellow;");
this.setTextFill(Color.BLACK);
} else if (item.equals("< 1 hour ago")) {
this.setStyle("-fx-background-color: green;");
//this.setTextFill(Color.GREEN);
}
setText(item);
}
}
};
}
});
column4.setCellValueFactory(param -> param.getValue().condition);
column4.setPrefWidth(100);
sensorsTable.getColumns().addAll(userid, resourceid, column1, column2, column3, column4);
}
}
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public SimpleStringProperty condition;
public Sensor(String userid, String resourceid, String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty(userid);
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
//we can use empty string or condition 3 here
}
public Sensor(String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty("-1");
this.resourceid = new SimpleStringProperty("-1");
this.sid = new SimpleStringProperty(sid);
this.timestamp= new SimpleStringProperty(timestamp);
this.reading= new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
}
public String getUserid() { return this.userid.toString(); }
public String getResourceid() { return this.resourceid.toString(); }
public String getSid() { return this.sid.toString(); }
public String getTimestamp() { return this.timestamp.toString(); }
public String getReading() { return this.reading.toString(); }
public String getCondition() { return this.condition.toString(); }
public String toString() { return "userid: "+getUserid()+" resourceid: "+getResourceid()+" sid: "+getSid()+
"\ntimestamp: "+getTimestamp()+" reading: "+getReading()+" condition: "+getCondition();}
}
单独 class:
public class tester {
public static void main(String[] args) {
Application.launch(TouchDisplayEmulatorController.class, args);
}
}
首先,不清楚 Pagination
是否是正确的控件,尽管第 selection 页的 UI 非常好。您的问题可能源于您每次都将 TableView
放入新的 BorderPane
,或者可能源于您正在使用 TableView.setItems()
.
一种有效的方法是使用 FilteredList
来处理分页,并且只将 TableView
作为布局中的静态元素,而不是在分页的动态图形区域中。为了满足让 Pagination
做某事的需要,创建了带有页码的 Text
。
新的 属性 已添加到传感器 - ordinalNumber
。这用于控制分页的过滤器。过滤器将动态更改为 select 只有那些在特定范围内具有 ordinalNumber
的传感器。范围由分页的 currentPageIndexProperty
控制。 属性 上有一个侦听器,每次更改页面时都会重新生成 FilteredList
的谓词 属性。
这处理了页面更改,但是对整个列表进行排序呢?首先,FilteredList
被包裹在 SortedList
中,SortedList
被设置到 TableView
中。 SortedList
的 Comparator
绑定到 TableView
的 Comparator
。
但是 SortedList
只能看到包含在当前过滤器下的传感器。因此 TableView
的 comparatorProperty
添加了一个监听器。此侦听器的操作流式传输底层 ObservableList
,使用新的 Comparator
对其进行排序,并根据新的排序顺序重置每个传感器的 ordinalNumber
。
最后,为了让 FilteredList
重新评估 ObservableList
,这些 ordinalNumber
更改需要触发 ListChange
事件。因此,在 ordinalNumber
.
的基础上,向 ObservableList
添加了一个提取器
结果非常好,除了愚蠢的页码 Text
每次换页都会滑到屏幕上。
为提高可读性清理了整个代码,并删除了未使用的内容以保持示例最少。
这是传感器 class:
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public IntegerProperty ordinalNumber = new SimpleIntegerProperty(0);
public Sensor(int userid, String resourceid, String sid, String timestamp, String reading, String condition) {
this.userid = new SimpleStringProperty(Integer.toString(userid));
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.ordinalNumber.set(userid);
}
}
布局代码如下:
public class PaginationController extends Application {
public TableView<Sensor> sensorsTable = new TableView<>();
public ObservableList<Sensor> sensorObservableList = FXCollections.observableArrayList(sensor -> new Observable[]{sensor.ordinalNumber});
public FilteredList<Sensor> sensorFilteredList = new FilteredList<>(sensorObservableList);
public SortedList<Sensor> sensorSortedList = new SortedList<>(sensorFilteredList);
public IntegerProperty currentPage = new SimpleIntegerProperty(0);
public int rowsPerPage = 14;
public void start(final Stage stage) throws Exception {
processSensors();
stage.setScene(new Scene(buildScene(), 1024, 768));
stage.setTitle("Table pager");
stage.show();
}
public Region buildScene() {
buildTable();
int numOfPages = calculateNumOfPages();
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(pageIndex -> {
Text text = new Text("This is page " + (pageIndex + 1));
return text;
});
pagination.setMaxPageIndicatorCount(numOfPages);
currentPage.bind(pagination.currentPageIndexProperty());
sensorFilteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> createPageFilter(pagination.getCurrentPageIndex()), pagination.currentPageIndexProperty()));
return new VBox(sensorsTable, pagination);
}
@NotNull
private Predicate<Sensor> createPageFilter(int currentPage) {
int lowerLimit = (currentPage) * rowsPerPage;
int upperLimit = (currentPage + 1) * rowsPerPage;
return sensor -> (sensor.ordinalNumber.get() >= lowerLimit) &&
(sensor.ordinalNumber.get() < upperLimit);
}
private int calculateNumOfPages() {
int numOfPages = 1;
if (sensorObservableList.size() % rowsPerPage == 0) {
numOfPages = sensorObservableList.size() / rowsPerPage;
} else if (sensorObservableList.size() > rowsPerPage) {
numOfPages = sensorObservableList.size() / rowsPerPage + 1;
}
return numOfPages;
}
public void processSensors() {
Random random = new Random();
for (int i = 0; i < 60; i++) {
sensorObservableList.add(new Sensor(i, "rid-" + i, "sid-" + i, Integer.toString(random.nextInt(100)), "0", "no condition"));
}
}
public void buildTable() {
addStringColumn("userid", param1 -> param1.getValue().userid);
addStringColumn("resourceid", param1 -> param1.getValue().resourceid);
addStringColumn("sid", param1 -> param1.getValue().sid);
addStringColumn("timestamp", param1 -> param1.getValue().timestamp);
addStringColumn("reading", param1 -> param1.getValue().reading);
TableColumn<Sensor, Number> ordinalCol = new TableColumn<>("ordinal");
ordinalCol.setCellValueFactory(param -> param.getValue().ordinalNumber);
ordinalCol.setPrefWidth(100);
sensorsTable.getColumns().add(ordinalCol);
sensorsTable.setItems(sensorSortedList);
sensorSortedList.comparatorProperty().bind(sensorsTable.comparatorProperty());
sensorSortedList.comparatorProperty().addListener(x -> renumberRecords());
}
private void renumberRecords() {
AtomicInteger counter = new AtomicInteger(0);
Comparator<Sensor> newValue = sensorsTable.getComparator();
if (newValue != null) {
sensorObservableList.stream().sorted(newValue).forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
} else {
sensorObservableList.forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
}
}
@NotNull
private void addStringColumn(String columnTitle, Callback<TableColumn.CellDataFeatures<Sensor, String>, ObservableValue<String>> callback) {
TableColumn<Sensor, String> column = new TableColumn<>(columnTitle);
column.setCellValueFactory(callback);
column.setPrefWidth(100);
sensorsTable.getColumns().add(column);
}
}
出于演示目的,传感器中的时间戳字段被初始化为随机数,以便在对该列进行排序时给出明显的变化。此外,ordinalNumber 字段已添加到 table,以便在选择新的排序列时可以轻松验证它们是否已重新评估。
不直接支持TableView的分页,所以我们必须自己做。注意问题中引用的解决方案 pre-date the (re-introduction of Sorted-/FilteredList)
如今,基本方法是使用仅包含当前页面上的行的 FilteredList。此 filteredList 必须是 table 的 itemsProperty 的值。为了也允许排序,我们需要将原始数据包装到一个 SortedList 并将其比较器绑定到 table 提供的比较器。合并所有:
items = observableArrayList(... //my data);
sortedList = new SortedList(items);
filteredList = new FilteredList(sortedList);
table.setItems(filteredList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
看起来不错,不是吗?不幸的是,单击列 header 时没有任何反应。原因:
- 负责排序的协作者是 sortPolicy
- 默认策略检查 table 的项目是否是排序列表:如果是(并且其比较器绑定到 table),则排序留给该列表,否则它会回到
FXCollections.sort(items, ...)
- collections.sort 无法执行任何操作,因为筛选列表不可修改
在伪代码中:
if (items instanceof SortedList) {
return sortedList.getComparator().isBoundTo(table.getComparator());
}
try {
FXCollections.sort(items);
// sorting succeeded
return true;
} catch (Exception ex) {
// sorting failed
return false;
}
出路是实施自定义排序策略:它不是只检查 table 的项目是否为排序列表,而是沿着 transformationList(如果可用)源链向上移动,直到找到一个已排序(或未排序):
ObservableList<?> lookup = items;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
items = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
// ... same as original policy
现在我们已经准备好了(完整列表的)排序 - 下一个问题是排序后分页视图应该发生什么。选项:
- 保持页面不变并更新过滤器
- 保持任何当前项目可见并更新页面
两者都需要在列表的排序状态发生变化时触发更新,具体实现取决于用户体验指南。
一个可运行的例子:
public class TableWithPaginationSO extends Application {
public static <T> Callback<TableView<T>, Boolean> createSortPolicy(TableView<T> table) {
// c&p of DEFAULT_SORT_POLICY except adding search up a chain
// of transformation lists until we find a sortedList
return new Callback<TableView<T>, Boolean>() {
@Override
public Boolean call(TableView<T> table) {
try {
ObservableList<?> itemsList = table.getItems();
// walk up the source lists to find the first sorted
ObservableList<?> lookup = itemsList;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
itemsList = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
if (itemsList instanceof SortedList) {
SortedList<?> sortedList = (SortedList<?>) itemsList;
boolean comparatorsBound = sortedList.comparatorProperty()
.isEqualTo(table.comparatorProperty()).get();
return comparatorsBound;
} else {
if (itemsList == null || itemsList.isEmpty()) {
// sorting is not supported on null or empty lists
return true;
}
Comparator comparator = table.getComparator();
if (comparator == null) {
return true;
}
// otherwise we attempt to do a manual sort, and if successful
// we return true
FXCollections.sort(itemsList, comparator);
return true;
}
} catch (UnsupportedOperationException e) {
return false;
}
};
};
}
private Parent createContent() {
initData();
// wrap sorted list around data
sorted = new SortedList<>(data);
// wrap filtered list around sorted
filtered = new FilteredList<>(sorted);
// use filtered as table's items
table = new TableView<>(filtered);
addColumns();
page = new BorderPane(table);
// install custom sort policy
table.setSortPolicy(createSortPolicy(table));
// bind sorted comparator to table's
sorted.comparatorProperty().bind(table.comparatorProperty());
pagination = new Pagination(rowsPerPage, 0);
pagination.setPageCount(sorted.size() / rowsPerPage);;
pagination.setPageFactory(this::createPage);
sorted.addListener((ListChangeListener<Locale>) c -> {
// update page after changes to list
updatePage(true);
});
return pagination;
}
private Node createPage(int pageIndex) {
updatePredicate(pageIndex);
return page;
}
/**
* Update the filter to show the current page.
*/
private void updatePredicate(int pageIndex) {
int first = rowsPerPage * pageIndex;
int last = Math.min(first + rowsPerPage, sorted.size());
Predicate<Locale> predicate = loc -> {
int index = sorted.indexOf(loc);
return index >= first && index < last;
};
filtered.setPredicate(predicate);
// keep reference to first on page
firstOnPage = filtered.get(0);
}
/**
* Update the page after changes to the list.
*/
private void updatePage(boolean keepItemVisible) {
if (keepItemVisible) {
int sortedIndex = sorted.indexOf(firstOnPage);
int pageIndex = sortedIndex >= 0 ? sortedIndex / rowsPerPage : 0;
pagination.setCurrentPageIndex(pageIndex);
} else {
updatePredicate(pagination.getCurrentPageIndex());
}
}
private void addColumns() {
TableColumn<Locale, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
table.getColumns().addAll(name, country);
}
private void initData() {
Locale[] availableLocales = Locale.getAvailableLocales();
data = observableArrayList(
Arrays.stream(availableLocales)
.filter(e -> e.getDisplayName().length() > 0)
.limit(120)
.collect(toList())
);
}
private TableView<Locale> table;
private Pagination pagination;
private BorderPane page;
private ObservableList<Locale> data;
private FilteredList<Locale> filtered;
private SortedList<Locale> sorted;
private Locale firstOnPage;
private int rowsPerPage = 15;
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我正在尝试实现一个允许按 JavaFX 中的所有项目排序的分页 TableView。我从这里实现了分页 table 视图:。由 jewelsea 和 tim buthe 提供。
我在想,因为 table 视图只访问项目的子列表,所以我想根据我对关于 Java 文档排序的部分:https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableView.html#setItems-javafx.collections.ObservableList-
// bind the sortedList comparator to the TableView comparator
//i m guessing it extends the sorting from the table to the actual list?
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
然后为相同的子列表索引刷新 table视图(由于整个列表已排序,现在应该对其进行排序)。
基本上,我想使用 table 列比较器对完整列表进行排序,然后使用新的排序列表“刷新”table 视图。这可行吗?或者有更简单的方法来解决这个问题吗?
我还参考了其他参考资料 material,例如:https://incepttechnologies.blogspot.com/p/javafx-tableview-with-pagination-and.html 但我发现它很难理解,因为到处都是含糊不清的解释。
快速提取我的 TouchDisplayEmulatorController 中的核心组件 class
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.List;
public class TouchDisplayEmulatorController extends Application {
public TableView sensorsTable;
public List<Sensor> sensors;
public int rowsPerPage = 14;
public GridPane grids = new GridPane();
public long timenow;
public void start(final Stage stage) throws Exception {
grids = new GridPane();
setGridPane();
Scene scene = new Scene(grids, 1024, 768);
stage.setScene(scene);
stage.setTitle("Table pager");
stage.show();
}
//public static void main(String[] args) throws Exception {
// launch(args);
//}
public void setGridPane(){
processSensors();
sensorsGrid();
}
public void sensorsGrid(){
buildTable();
int numOfPages = 1;
if (sensors.size() % rowsPerPage == 0) {
numOfPages = sensors.size() / rowsPerPage;
} else if (sensors.size() > rowsPerPage) {
numOfPages = sensors.size() / rowsPerPage + 1;
}
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(this::createPage);
pagination.setMaxPageIndicatorCount(numOfPages);
grids.add(pagination, 0, 0);
}
private Node createPage(int pageIndex) {
int fromIndex = pageIndex * rowsPerPage;
int toIndex = Math.min(fromIndex + rowsPerPage, sensors.size());
sensorsTable.setItems(FXCollections.observableArrayList(sensors.subList(fromIndex, toIndex)));
return new BorderPane(sensorsTable);
}
public void processSensors(){
sensors = new ArrayList<>();
// long timenow = OffsetDateTime.now(ZoneOffset.UTC).toInstant().toEpochMilli()/1000;
// StringTokenizer hildetoken = new StringTokenizer(msg);
for (int i=0; i<20; i++) {
sensors.add(new Sensor(String.valueOf(i), "rid-"+i, "sid-"+i, "0", "0", "no condition"));
}
}
public void buildTable() {
sensorsTable = new TableView();
TableColumn<Sensor, String> userid = new TableColumn<>("userid");
userid.setCellValueFactory(param -> param.getValue().userid);
userid.setPrefWidth(100);
TableColumn<Sensor, String> resourceid = new TableColumn<>("resourceid");
resourceid.setCellValueFactory(param -> param.getValue().resourceid);
resourceid.setPrefWidth(100);
TableColumn<Sensor, String> column1 = new TableColumn<>("sid");
column1.setCellValueFactory(param -> param.getValue().sid);
column1.setPrefWidth(100);
TableColumn<Sensor, String> column2 = new TableColumn<>("timestamp");
column2.setCellValueFactory(param -> param.getValue().timestamp);
column2.setPrefWidth(100);
TableColumn<Sensor, String> column3 = new TableColumn<>("reading");
column3.setCellValueFactory(param -> param.getValue().reading);
column3.setPrefWidth(100);
TableColumn<Sensor, String> column4 = new TableColumn<>("last contacted");
column4.setCellFactory(new Callback<TableColumn<Sensor, String>, TableCell<Sensor, String>>() {
@Override
public TableCell<Sensor, String> call(TableColumn<Sensor, String> sensorStringTableColumn) {
return new TableCell<Sensor, String>() {
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
this.setTextFill(Color.WHITE);
if (item.contains("@")) {
this.setTextFill(Color.BLUEVIOLET);
} else if (item.equals("> 8 hour ago")) {
this.setStyle("-fx-background-color: red;");
} else if (item.equals("< 8 hour ago")) {
this.setStyle("-fx-background-color: orange;");
//this.setTextFill(Color.ORANGE);
} else if (item.equals("< 4 hour ago")) {
this.setStyle("-fx-background-color: yellow;");
this.setTextFill(Color.BLACK);
} else if (item.equals("< 1 hour ago")) {
this.setStyle("-fx-background-color: green;");
//this.setTextFill(Color.GREEN);
}
setText(item);
}
}
};
}
});
column4.setCellValueFactory(param -> param.getValue().condition);
column4.setPrefWidth(100);
sensorsTable.getColumns().addAll(userid, resourceid, column1, column2, column3, column4);
}
}
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public SimpleStringProperty condition;
public Sensor(String userid, String resourceid, String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty(userid);
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
//we can use empty string or condition 3 here
}
public Sensor(String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty("-1");
this.resourceid = new SimpleStringProperty("-1");
this.sid = new SimpleStringProperty(sid);
this.timestamp= new SimpleStringProperty(timestamp);
this.reading= new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
}
public String getUserid() { return this.userid.toString(); }
public String getResourceid() { return this.resourceid.toString(); }
public String getSid() { return this.sid.toString(); }
public String getTimestamp() { return this.timestamp.toString(); }
public String getReading() { return this.reading.toString(); }
public String getCondition() { return this.condition.toString(); }
public String toString() { return "userid: "+getUserid()+" resourceid: "+getResourceid()+" sid: "+getSid()+
"\ntimestamp: "+getTimestamp()+" reading: "+getReading()+" condition: "+getCondition();}
}
单独 class:
public class tester {
public static void main(String[] args) {
Application.launch(TouchDisplayEmulatorController.class, args);
}
}
首先,不清楚 Pagination
是否是正确的控件,尽管第 selection 页的 UI 非常好。您的问题可能源于您每次都将 TableView
放入新的 BorderPane
,或者可能源于您正在使用 TableView.setItems()
.
一种有效的方法是使用 FilteredList
来处理分页,并且只将 TableView
作为布局中的静态元素,而不是在分页的动态图形区域中。为了满足让 Pagination
做某事的需要,创建了带有页码的 Text
。
新的 属性 已添加到传感器 - ordinalNumber
。这用于控制分页的过滤器。过滤器将动态更改为 select 只有那些在特定范围内具有 ordinalNumber
的传感器。范围由分页的 currentPageIndexProperty
控制。 属性 上有一个侦听器,每次更改页面时都会重新生成 FilteredList
的谓词 属性。
这处理了页面更改,但是对整个列表进行排序呢?首先,FilteredList
被包裹在 SortedList
中,SortedList
被设置到 TableView
中。 SortedList
的 Comparator
绑定到 TableView
的 Comparator
。
但是 SortedList
只能看到包含在当前过滤器下的传感器。因此 TableView
的 comparatorProperty
添加了一个监听器。此侦听器的操作流式传输底层 ObservableList
,使用新的 Comparator
对其进行排序,并根据新的排序顺序重置每个传感器的 ordinalNumber
。
最后,为了让 FilteredList
重新评估 ObservableList
,这些 ordinalNumber
更改需要触发 ListChange
事件。因此,在 ordinalNumber
.
ObservableList
添加了一个提取器
结果非常好,除了愚蠢的页码 Text
每次换页都会滑到屏幕上。
为提高可读性清理了整个代码,并删除了未使用的内容以保持示例最少。
这是传感器 class:
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public IntegerProperty ordinalNumber = new SimpleIntegerProperty(0);
public Sensor(int userid, String resourceid, String sid, String timestamp, String reading, String condition) {
this.userid = new SimpleStringProperty(Integer.toString(userid));
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.ordinalNumber.set(userid);
}
}
布局代码如下:
public class PaginationController extends Application {
public TableView<Sensor> sensorsTable = new TableView<>();
public ObservableList<Sensor> sensorObservableList = FXCollections.observableArrayList(sensor -> new Observable[]{sensor.ordinalNumber});
public FilteredList<Sensor> sensorFilteredList = new FilteredList<>(sensorObservableList);
public SortedList<Sensor> sensorSortedList = new SortedList<>(sensorFilteredList);
public IntegerProperty currentPage = new SimpleIntegerProperty(0);
public int rowsPerPage = 14;
public void start(final Stage stage) throws Exception {
processSensors();
stage.setScene(new Scene(buildScene(), 1024, 768));
stage.setTitle("Table pager");
stage.show();
}
public Region buildScene() {
buildTable();
int numOfPages = calculateNumOfPages();
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(pageIndex -> {
Text text = new Text("This is page " + (pageIndex + 1));
return text;
});
pagination.setMaxPageIndicatorCount(numOfPages);
currentPage.bind(pagination.currentPageIndexProperty());
sensorFilteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> createPageFilter(pagination.getCurrentPageIndex()), pagination.currentPageIndexProperty()));
return new VBox(sensorsTable, pagination);
}
@NotNull
private Predicate<Sensor> createPageFilter(int currentPage) {
int lowerLimit = (currentPage) * rowsPerPage;
int upperLimit = (currentPage + 1) * rowsPerPage;
return sensor -> (sensor.ordinalNumber.get() >= lowerLimit) &&
(sensor.ordinalNumber.get() < upperLimit);
}
private int calculateNumOfPages() {
int numOfPages = 1;
if (sensorObservableList.size() % rowsPerPage == 0) {
numOfPages = sensorObservableList.size() / rowsPerPage;
} else if (sensorObservableList.size() > rowsPerPage) {
numOfPages = sensorObservableList.size() / rowsPerPage + 1;
}
return numOfPages;
}
public void processSensors() {
Random random = new Random();
for (int i = 0; i < 60; i++) {
sensorObservableList.add(new Sensor(i, "rid-" + i, "sid-" + i, Integer.toString(random.nextInt(100)), "0", "no condition"));
}
}
public void buildTable() {
addStringColumn("userid", param1 -> param1.getValue().userid);
addStringColumn("resourceid", param1 -> param1.getValue().resourceid);
addStringColumn("sid", param1 -> param1.getValue().sid);
addStringColumn("timestamp", param1 -> param1.getValue().timestamp);
addStringColumn("reading", param1 -> param1.getValue().reading);
TableColumn<Sensor, Number> ordinalCol = new TableColumn<>("ordinal");
ordinalCol.setCellValueFactory(param -> param.getValue().ordinalNumber);
ordinalCol.setPrefWidth(100);
sensorsTable.getColumns().add(ordinalCol);
sensorsTable.setItems(sensorSortedList);
sensorSortedList.comparatorProperty().bind(sensorsTable.comparatorProperty());
sensorSortedList.comparatorProperty().addListener(x -> renumberRecords());
}
private void renumberRecords() {
AtomicInteger counter = new AtomicInteger(0);
Comparator<Sensor> newValue = sensorsTable.getComparator();
if (newValue != null) {
sensorObservableList.stream().sorted(newValue).forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
} else {
sensorObservableList.forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
}
}
@NotNull
private void addStringColumn(String columnTitle, Callback<TableColumn.CellDataFeatures<Sensor, String>, ObservableValue<String>> callback) {
TableColumn<Sensor, String> column = new TableColumn<>(columnTitle);
column.setCellValueFactory(callback);
column.setPrefWidth(100);
sensorsTable.getColumns().add(column);
}
}
出于演示目的,传感器中的时间戳字段被初始化为随机数,以便在对该列进行排序时给出明显的变化。此外,ordinalNumber 字段已添加到 table,以便在选择新的排序列时可以轻松验证它们是否已重新评估。
不直接支持TableView的分页,所以我们必须自己做。注意问题中引用的解决方案 pre-date the (re-introduction of Sorted-/FilteredList)
如今,基本方法是使用仅包含当前页面上的行的 FilteredList。此 filteredList 必须是 table 的 itemsProperty 的值。为了也允许排序,我们需要将原始数据包装到一个 SortedList 并将其比较器绑定到 table 提供的比较器。合并所有:
items = observableArrayList(... //my data);
sortedList = new SortedList(items);
filteredList = new FilteredList(sortedList);
table.setItems(filteredList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
看起来不错,不是吗?不幸的是,单击列 header 时没有任何反应。原因:
- 负责排序的协作者是 sortPolicy
- 默认策略检查 table 的项目是否是排序列表:如果是(并且其比较器绑定到 table),则排序留给该列表,否则它会回到
FXCollections.sort(items, ...)
- collections.sort 无法执行任何操作,因为筛选列表不可修改
在伪代码中:
if (items instanceof SortedList) {
return sortedList.getComparator().isBoundTo(table.getComparator());
}
try {
FXCollections.sort(items);
// sorting succeeded
return true;
} catch (Exception ex) {
// sorting failed
return false;
}
出路是实施自定义排序策略:它不是只检查 table 的项目是否为排序列表,而是沿着 transformationList(如果可用)源链向上移动,直到找到一个已排序(或未排序):
ObservableList<?> lookup = items;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
items = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
// ... same as original policy
现在我们已经准备好了(完整列表的)排序 - 下一个问题是排序后分页视图应该发生什么。选项:
- 保持页面不变并更新过滤器
- 保持任何当前项目可见并更新页面
两者都需要在列表的排序状态发生变化时触发更新,具体实现取决于用户体验指南。
一个可运行的例子:
public class TableWithPaginationSO extends Application {
public static <T> Callback<TableView<T>, Boolean> createSortPolicy(TableView<T> table) {
// c&p of DEFAULT_SORT_POLICY except adding search up a chain
// of transformation lists until we find a sortedList
return new Callback<TableView<T>, Boolean>() {
@Override
public Boolean call(TableView<T> table) {
try {
ObservableList<?> itemsList = table.getItems();
// walk up the source lists to find the first sorted
ObservableList<?> lookup = itemsList;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
itemsList = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
if (itemsList instanceof SortedList) {
SortedList<?> sortedList = (SortedList<?>) itemsList;
boolean comparatorsBound = sortedList.comparatorProperty()
.isEqualTo(table.comparatorProperty()).get();
return comparatorsBound;
} else {
if (itemsList == null || itemsList.isEmpty()) {
// sorting is not supported on null or empty lists
return true;
}
Comparator comparator = table.getComparator();
if (comparator == null) {
return true;
}
// otherwise we attempt to do a manual sort, and if successful
// we return true
FXCollections.sort(itemsList, comparator);
return true;
}
} catch (UnsupportedOperationException e) {
return false;
}
};
};
}
private Parent createContent() {
initData();
// wrap sorted list around data
sorted = new SortedList<>(data);
// wrap filtered list around sorted
filtered = new FilteredList<>(sorted);
// use filtered as table's items
table = new TableView<>(filtered);
addColumns();
page = new BorderPane(table);
// install custom sort policy
table.setSortPolicy(createSortPolicy(table));
// bind sorted comparator to table's
sorted.comparatorProperty().bind(table.comparatorProperty());
pagination = new Pagination(rowsPerPage, 0);
pagination.setPageCount(sorted.size() / rowsPerPage);;
pagination.setPageFactory(this::createPage);
sorted.addListener((ListChangeListener<Locale>) c -> {
// update page after changes to list
updatePage(true);
});
return pagination;
}
private Node createPage(int pageIndex) {
updatePredicate(pageIndex);
return page;
}
/**
* Update the filter to show the current page.
*/
private void updatePredicate(int pageIndex) {
int first = rowsPerPage * pageIndex;
int last = Math.min(first + rowsPerPage, sorted.size());
Predicate<Locale> predicate = loc -> {
int index = sorted.indexOf(loc);
return index >= first && index < last;
};
filtered.setPredicate(predicate);
// keep reference to first on page
firstOnPage = filtered.get(0);
}
/**
* Update the page after changes to the list.
*/
private void updatePage(boolean keepItemVisible) {
if (keepItemVisible) {
int sortedIndex = sorted.indexOf(firstOnPage);
int pageIndex = sortedIndex >= 0 ? sortedIndex / rowsPerPage : 0;
pagination.setCurrentPageIndex(pageIndex);
} else {
updatePredicate(pagination.getCurrentPageIndex());
}
}
private void addColumns() {
TableColumn<Locale, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
table.getColumns().addAll(name, country);
}
private void initData() {
Locale[] availableLocales = Locale.getAvailableLocales();
data = observableArrayList(
Arrays.stream(availableLocales)
.filter(e -> e.getDisplayName().length() > 0)
.limit(120)
.collect(toList())
);
}
private TableView<Locale> table;
private Pagination pagination;
private BorderPane page;
private ObservableList<Locale> data;
private FilteredList<Locale> filtered;
private SortedList<Locale> sorted;
private Locale firstOnPage;
private int rowsPerPage = 15;
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}