整个列表的分页 TableView 排序出错

我正在尝试实现一个分页 TableView,它允许按 JavaFX 中的所有项目进行排序。我从这里实现了分页 tableview:https://stackoverflow.com/a/25424208/12181863。由jewelsea 和tim buthe 提供。

我在想,因为表视图只访问项目的子列表,所以我想根据我在 Java 文档中关于排序的部分的理解,将排序从表列扩展到完整列表:{{ 3}}

 // 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());

然后刷新 tableview 以获取相同的子列表索引(由于整个列表已排序,因此现在应该对其进行排序)。

基本上,我想使用表格列比较器对完整列表进行排序,然后使用新的排序列表“刷新”tableview。这可行吗?或者有没有更简单的方法来解决这个问题?

我还参考了其他参考资料,例如:https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableView.html#setItems-javafx.collections.ObservableList-,但我发现很难理解,因为所有内容都随处可见,而且解释含糊不清。

快速提取我的 TouchDisplayEmulatorController 类中的核心组件

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

    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","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 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();}
}

单独的类:

public class tester {
    public static void main(String[] args) {
        Application.launch(TouchDisplayEmulatorController.class,args);
    }
}
a962319828 回答:整个列表的分页 TableView 排序出错

TableView 的分页是不直接支持的,所以我们必须自己做。请注意,问题中引用的解决方案早于 (re-introduction of Sorted-/FilteredList)

如今,基本方法是使用仅包含当前页面上的行的 FilteredList。这个filteredList 必须是表的itemsProperty 的值。为了也允许排序,我们需要将原始数据包装到一个 SortedList 并将其比较器绑定到表提供的比较器。综合所有:

items = observableArrayList(... //my data);
sortedList = new SortedList(items);
filteredList = new FilteredList(sortedList);
table.setItems(filteredList);
sortedList.comparatorProperty().bind(table.comparatorProperty());

看起来不错,不是吗?不幸的是,单击列标题时没有任何反应。原因:

  • 负责排序的协作者是 sortPolicy
  • 默认策略检查表的项目是否是一个排序列表:如果是(并且它的比较器绑定到表的),排序留给该列表,否则返回到 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;
}

出路是实现自定义排序策略:它不仅检查表的项是否为 sortedList,而是沿着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);
    }

}
,

首先,不清楚 Pagination 是否是正确的控件,尽管页面选择的 UI 非常好。您的问题可能源于您每次都将 TableView 放入新的 BorderPane 中,也可能源于您使用了 TableView.setItems()

一种有效的方法是使用 FilteredList 来处理分页,并将 TableView 保持为布局中的静态元素,而不是分页的动态图形区域。为了满足让 Pagination 做某事的需要,创建了一个带有页码的 Text

传感器添加了一个新属性 - ordinalNumber。这用于控制分页的过滤器。过滤器将动态更改以仅选择特定范围内具有 ordinalNumber 的传感器。范围由分页的 currentPageIndexProperty 控制。该属性上有一个侦听器,每次更改页面时都会重新生成 FilteredList 的谓词属性。

它处理页面变化,但是对整个列表进行排序呢?首先,FilteredList 包含在 SortedList 中,SortedList 被设置到 TableView 中。 SortedListComparator 绑定到 TableViewComparator

但是 SortedList 只能看到当前过滤器下包含的传感器。因此,在 TableViewcomparatorProperty 中添加了一个侦听器。此侦听器的操作流式传输基础 ObservableList,使用新的 Comparator 对其进行排序,并根据新的排序顺序重置每个 Sensor 的 ordinalNumber

最后,为了让 FilteredList 重新评估 ObservableList,这些 ordinalNumber 更改需要触发 ListChange 事件。因此,基于 ObservableListordinalNumber 添加了一个提取器。

结果效果很好,除了页面编号 Text 在每次页面更改时滑动到屏幕上。

为了可读性而清理了整个代码,并去除了未使用的东西以保持示例最小化。

这是传感器类:

    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 字段,以便在选择新的排序列时可以轻松验证它们是否已重新计算。

本文链接:https://www.f2er.com/17546.html

大家都在问