我有两个表,两个表必须具有相同的列顺序。用户可以更改第一个表中的列顺序。在这种情况下,程序应检测第一个表中的列顺序变化,并更改第二个表中的列顺序。
问题是,当我在第一个表中使用鼠标更改列顺序时,
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;
}
}
谁能告诉我如何解决吗?
不幸的是,当前通过用户交互实现列重新排序的方式会导致“替换”更改,而不是“排列”更改。而且我认为您不能在不修改(如果不能完全替换)默认皮肤的情况下强制进行排列更改。但您实际上可以根据替换更改提供的信息计算排列。例如:
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;
}
}
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);
}
}