TableView 列列表的 Change.wasPermutated() 在列顺序更改时始终返回 false

问题描述 投票:0回答:1

我有两个表,两个表必须具有相同的列顺序。用户可以更改第一个表中的列顺序。在这种情况下,程序应检测第一个表中的列顺序变化,并更改第二个表中的列顺序。

问题是,当我在第一个表中使用鼠标更改列顺序时,

change.wasPermutated()
始终返回
false
。这是我的代码:

public class JavaFxTest7 extends Application {

    private static class Student {

        private int id;

        private String name;

        private int mark;

        public Student(int id, String name, int mark) {
            this.id = id;
            this.name = name;
            this.mark = mark;
        }

        public int getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public int getMark() {
            return mark;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        var table1 = this.createTable();
        var table2 = this.createTable();
        table1.getColumns().addListener((ListChangeListener<? super TableColumn<Student, ?>>)(change) -> {
            while (change.next()) {
                System.out.println("wasPermutated:" + change.wasPermutated());
                if (change.wasPermutated()) {
                    int from = change.getFrom();
                    int to = change.getTo();
                    System.out.println("from: " + from + " to: " + to);
                }
            }
        });
        var scene = new Scene(new VBox(table1, table2), 300, 300);
        stage.setScene(scene);
        stage.show();
    }

    private TableView<Student> createTable() {
        var table = new TableView<Student>(FXCollections.observableList(Arrays.asList(new Student(1, "Billy", 3),
                new Student(2, "Johnny", 4), new Student(3, "Mickey", 5))));
        var idColumn = new TableColumn<Student, Integer>("ID");
        idColumn.setCellValueFactory((data) ->  new ReadOnlyObjectWrapper<>(data.getValue().getId()));
        var nameColumn = new TableColumn<Student, String>("Name");
        nameColumn.setCellValueFactory((data) ->  new ReadOnlyStringWrapper(data.getValue().getName()));
        var markColumn = new TableColumn<Student, Integer>("Mark");
        markColumn.setCellValueFactory((data) ->  new ReadOnlyObjectWrapper<>(data.getValue().getMark()));
        table.getColumns().addAll(idColumn, nameColumn, markColumn);
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
        return table;
    }
}

谁能告诉我如何解决吗?

java javafx
1个回答
2
投票

不幸的是,当前通过用户交互实现列重新排序的方式会导致“替换”更改,而不是“排列”更改。而且我认为您不能在不修改(如果不能完全替换)默认皮肤的情况下强制进行排列更改。但您实际上可以根据替换更改提供的信息计算排列。例如: int[] computePermutation(ListChangeListener.Change<?> change) { if (!change.wasReplaced()) { throw new IllegalArgumentException("not replacement change"); } var source = change.getList(); var removed = change.getRemoved(); int from = change.getFrom(); int to = change.getTo(); int[] perm = new int[to - from]; for (int index = from; index < to; index++) { perm[index - from] = source.indexOf(removed.get(index - from)); if (perm[index - from] == -1) { throw new IllegalStateException("replacement change was not a reordering"); } } return perm; } 然后您可以使用排列数组重新排序不同的

ObservableList
。例如:

@SuppressWarnings({"rawtypes", "unchecked"}) void reorder(ObservableList<?> target, int[] perm, int from, int to) { var rawTarget = (ObservableList) target; var temp = new ArrayList<Object>(target); for (int index = from; index < to; index++) { temp.set(perm[index - from], target.get(index)); } rawTarget.setAll(temp); }


概念验证
这是一个具有多向“绑定”的
概念验证

,其中一个列表的重新排序将导致所有其他列表的相同重新排序。

Main.java

package com.example; import java.util.ArrayList; import java.util.List; import java.util.Random; import javafx.application.Application; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Scene; import javafx.scene.control.SplitPane; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.util.Callback; public class Main extends Application { public record Student(int id, String name, int mark) {} @Override public void start(javafx.stage.Stage primaryStage) { var leftTable = createTable(); var middleTable = createTable(); var rightTable = createTable(); OrderBinding.bind(leftTable.getColumns(), middleTable.getColumns(), rightTable.getColumns()); var root = new SplitPane(leftTable, middleTable, rightTable); root.setDividerPositions(0.33, 0.66); primaryStage.setScene(new Scene(root, 1080, 720)); primaryStage.show(); } private TableView<Student> createTable() { var table = new TableView<Student>(); table.getItems().addAll(createStudents()); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN); table.getColumns().add(createColumn("ID", Student::id)); table.getColumns().add(createColumn("Name", Student::name)); table.getColumns().add(createColumn("Mark", Student::mark)); return table; } private <T> TableColumn<Student, T> createColumn( String name, Callback<Student, T> valueExtractor) { var column = new TableColumn<Student, T>(name); column.setEditable(false); column.setCellValueFactory( cdf -> { var value = valueExtractor.call(cdf.getValue()); return new SimpleObjectProperty<>(value); }); return column; } private List<Student> createStudents() { var students = new ArrayList<Student>(); var random = new Random(); for (int i = 1; i <= 20; i++) { var name = "Student-%03d".formatted(i); int mark = random.nextInt(65, 101); students.add(new Student(i, name, mark)); } return students; } }

OrderBinding.java

package com.example; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.util.Subscription; // Added in JavaFX 21 /* * TODO: Consider implementing javafx.beans.WeakListener and holding * each target ObservableList in a WeakReference so that this * binding does not force the lists to stay in memory. */ public class OrderBinding implements ListChangeListener<Object> { public static Subscription bind(ObservableList<?>... targets) { return bind(List.of(targets)); } public static Subscription bind(Collection<? extends ObservableList<?>> targets) { var copy = List.copyOf(targets); if (copy.size() <= 1) { throw new IllegalArgumentException("must specify at least two targets"); } int requiredSize = copy.get(0).size(); for (var target : copy) { if (target.size() != requiredSize) { throw new IllegalArgumentException("all target lists must have the same size"); } } /* * TODO: Consider throwing an IllegalArgumentException if 'targets' contains * duplicate lists (by identity, i.e., ==). It would probably be a good * idea to perform this check at the same time as the size check (see above). * * That said, since 'reorder' is only invoked if the source list != the target * list, I don't believe duplicates will *break* anything. */ var listener = new OrderBinding(copy); copy.forEach(t -> t.addListener(listener)); return () -> copy.forEach(t -> t.removeListener(listener)); } private final List<? extends ObservableList<?>> targets; private boolean reordering; private OrderBinding(List<? extends ObservableList<?>> targets) { this.targets = targets; } @Override public void onChanged(ListChangeListener.Change<?> c) { if (!reordering) { reordering = true; try { while (c.next()) { if (c.wasPermutated() || c.wasReplaced()) { int[] perm = getPermutation(c); for (var target : targets) { if (target != c.getList()) { reorder(target, perm, c.getFrom(), c.getTo()); } } } else if (!c.wasUpdated()) { throw new IllegalStateException( "Lists bound with an OrderBinding must not have elements added or removed"); } } } finally { reordering = false; } } } private int[] getPermutation(ListChangeListener.Change<?> c) { assert c.wasPermutated() || c.wasReplaced(); var source = c.getList(); int from = c.getFrom(); int to = c.getTo(); int[] perm = new int[to - from]; if (c.wasPermutated()) { for (int i = from; i < to; i++) { perm[i - from] = c.getPermutation(i); } } else { var removed = c.getRemoved(); for (int i = from; i < to; i++) { int j = source.indexOf(removed.get(i - from)); if (j == -1) { throw new IllegalStateException("replacement change was not a reordering"); } perm[i - from] = j; } } return perm; } @SuppressWarnings({"rawtypes", "unchecked"}) private void reorder(ObservableList target, int[] perm, int from, int to) { var temp = new ArrayList<Object>(target); for (int i = from; i < to; i++) { temp.set(perm[i - from], target.get(i)); } target.setAll(temp); } }

注意
上面的所有代码都被设计为无论有多少元素被重新排序都可以工作。但是,由于用户一次只能通过 UI 移动一列,因此我怀疑如果这是您想要处理的唯一情况,则可以大大简化代码。

如果您只想“绑定”两个列表的顺序和/或只想“绑定”朝一个方向,也可以简化。

© www.soinside.com 2019 - 2024. All rights reserved.