我在JavaFX中使用TreeTableView类来实现分组股票代码监视列表。在排序时,我想只对股票进行排序(而不是组)。目前,当我点击“符号”列(例如)时,它会对每组中的股票进行排序(如我所料),但它也会对这些组进行排序。
在我的情况下,我想只在每组中分类股票,并保留我的观察名单中的组的顺序。
我尝试过使用setSortMode()方法,但它只支持以下模式:
ALL_DESCENDANTS
ONLY_FIRST_LEVEL
我看了一下TreeItem类的源代码,看起来目前还不支持按叶子排序(但可能会在以后)。
private void runSort(ObservableList<TreeItem<T>> children, Comparator<TreeItem<T>> comparator, TreeSortMode sortMode)
{
if (sortMode == ALL_DESCENDANTS) {
doSort(children, comparator);
} else if (sortMode == ONLY_FIRST_LEVEL) {
// if we are here we presume that the current node is the root node
// (but we can test to see if getParent() returns null to be sure).
// We also know that ONLY_FIRST_LEVEL only applies to the children
// of the root, so we return straight after we sort these children.
if (getParent() == null) {
doSort(children, comparator);
}
// } else if (sortMode == ONLY_LEAVES) {
// if (isLeaf()) {
// // sort the parent once
// }
// } else if (sortMode == ALL_BUT_LEAVES) {
} else {
// Unknown sort mode
}
}
有没有办法解决此限制而无需等待在将来的JavaFX更新中添加支持?
答案 0 :(得分:1)
很晚才回复,但也许您仍然可以使用反馈。我找到了两种你想要的方法。
选项1:您可以设置自己的排序策略。在这里,我设置了一个简单的策略,只对根直接位于节点下的节点的子节点进行排序。您必须为每个列和每个列组合实现排序方法。
package sample;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeSortMode;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
public class SortOnlySome extends Application {
private final TreeTableColumn<Person, String> tableColumn = new TreeTableColumn<>("Person");
private final TreeTableColumn<Person, Integer> ageColumn = new TreeTableColumn<>("Age");
@Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane();
TreeTableView<Person> treeTable = new TreeTableView();
borderPane.setCenter(treeTable);
setupColumns(treeTable);
TreeItem<Person> root = new TreeItem<>(new Person("Root"));
// Add some data
populate(root);
// Set the data into the treetable
treeTable.setRoot(root);
root.setExpanded(true);
root.getChildren().forEach(c -> c.setExpanded(true));
primaryStage.setScene(new Scene(borderPane));
primaryStage.setTitle("Dont sort categories");
primaryStage.show();
}
private void setupColumns(TreeTableView<Person> treeTable) {
tableColumn.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().getValue().displayName));
ageColumn.setCellValueFactory(param -> new ReadOnlyIntegerWrapper(param.getValue().getValue().age).asObject());
treeTable.getColumns().setAll(tableColumn, ageColumn);
treeTable.setSortPolicy(new Callback<TreeTableView<Person>, Boolean>() {
@Override
public Boolean call(TreeTableView<Person> table) {
TreeItem<Person> rootItem = table.getRoot();
if (rootItem == null) return false;
TreeSortMode sortMode = table.getSortMode();
if (sortMode == null) return false;
// Collecto the column comparators and merge them into 1
List<Comparator<Person>> comparators = new ArrayList<Comparator<Person>>();
table.getSortOrder().stream().forEachOrdered(ttc -> {
Comparator<Person> columnComparator = getComparatorForColumn(ttc);
comparators.add(columnComparator);
});
Comparator<Person> merged = mergeComparators(comparators);
rootItem.getChildren().forEach(c -> {
c.getChildren().sort((o1, o2) -> merged.compare(o1.getValue(), o2.getValue()));
// TODO: Sort recursively down c.getChildren()
});
return true;
}
});
}
private Comparator<Person> getComparatorForColumn(TreeTableColumn<Person, ?> column) {
int sign = column.getSortType() == TreeTableColumn.SortType.ASCENDING ? 1 : -1;
if (column == tableColumn) {
Comparator<Person> nameCompare = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
Comparator c = column.getComparator();
Object obj1 = o1.displayName;
Object obj2 = o2.displayName;
return sign * c.compare(obj1, obj2);
}
};
return nameCompare;
} else if (column == ageColumn) {
return new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return sign * Integer.compare(o1.age, o2.age);
}
};
} else {
// TODO: Comparators for other columns
return new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return sign * 0;
}
};
}
}
public static Comparator<Person> mergeComparators(final Collection<Comparator<Person>> multipleOptions) {
return new Comparator<Person>() {
public int compare(Person o1, Person o2) {
for (Comparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
private void populate(TreeItem<Person> root) {
root.getChildren().add(new TreeItem<>(new Person("Group A")));
root.getChildren().add(new TreeItem<>(new Person("Category 2")));
root.getChildren().add(new TreeItem<>(new Person("Collection C")));
// Add some people
for (int i = 0; i < 17; i++) {
String name = "Person " + (i + 1);
Person someone = new Person(name);
someone.age = 16 + i * 3;
TreeItem categoryNode = root.getChildren().get(i % 3);
categoryNode.getChildren().add(new TreeItem<>(someone));
}
}
public class Person {
public String displayName;
public int age;
public Person(String name) {
this.displayName = name;
}
}
public static void main(String[] args) {
launch(args);
}
}
选项2:替代方法......让我们看一下treetableview的功能。要比较元素,它会创建一个(新)TableColumnComparatorBase。在callstack中的某个时刻,类TableColumnComparatorBase将获取列的值并将它们用于比较:
@Override public int compare(S o1, S o2) {
for (TableColumnBase<S,T> tc : columns) {
if (! isSortable(tc)) continue;
T value1 = tc.getCellData(o1);
T value2 = tc.getCellData(o2);
int result = doCompare(tc, value1, value2);
if (result != 0) {
return result;
}
}
return 0;
}
类类TableColumnBase,它检查如何比较它们:
public static final Comparator DEFAULT_COMPARATOR = (obj1, obj2) -> {
if (obj1 == null && obj2 == null) return 0;
if (obj1 == null) return -1;
if (obj2 == null) return 1;
if (obj1 instanceof Comparable && (obj1.getClass() == obj2.getClass() || obj1.getClass().isAssignableFrom(obj2.getClass()))) {
return (obj1 instanceof String) ? Collator.getInstance().compare(obj1, obj2) : ((Comparable)obj1).compareTo(obj2);
}
return Collator.getInstance().compare(obj1.toString(), obj2.toString());
};
我找不到按列设置比较器的方法。但我们能做的是确保单元格中的值实现Comparable,并在它们不应移动时返回0。请注意,不是TreeItem的项目,而是CellValueFactory生成的项目应该实现Comparable。遗憾的是,这意味着我们需要添加自定义CellFactories或toStrings。它根本不是很漂亮,但在下面你会找到一个有效的例子:
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.text.Collator;
public class SortOnlySome extends Application {
@Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane();
TreeTableView<Person> treeTable = new TreeTableView();
borderPane.setCenter(treeTable);
setupColumns(treeTable);
TreeItem<Person> root = new TreeItem<>(new Person("Root", false));
// Add some data
populate(root);
// Set the data into the treetable
treeTable.setRoot(root);
root.setExpanded(true);
root.getChildren().forEach(c -> c.setExpanded(true));
primaryStage.setScene(new Scene(borderPane));
primaryStage.setTitle("Dont sort categories");
primaryStage.show();
}
private void setupColumns(TreeTableView<Person> treeTable) {
TreeTableColumn<Person, NameSortWrapper> treeColumn = new TreeTableColumn<>("Person");
treeColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(new NameSortWrapper(param.getValue().getValue())));
TreeTableColumn<Person, AgeSortWrapper> ageColumn = new TreeTableColumn<>("Age");
ageColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(new AgeSortWrapper(param.getValue().getValue())));
treeTable.getColumns().setAll(treeColumn, ageColumn);
}
private void populate(TreeItem<Person> root) {
root.getChildren().add(new TreeItem<>(new Person("Group A", false)));
root.getChildren().add(new TreeItem<>(new Person("Category 2", false)));
root.getChildren().add(new TreeItem<>(new Person("Collection C", false)));
// Add some people
for (int i = 0; i < 17; i++) {
String name = "Person " + (i + 1);
Person someone = new Person(name, true);
someone.age = 16 + i * 3;
TreeItem categoryNode = root.getChildren().get(i % 3);
categoryNode.getChildren().add(new TreeItem<>(someone));
}
}
// The class to show in the treetable
// Problem 1: The data class defines whether sorting is allowed
// Problem 2: Category nodes in the tree will also be TreeItem<Person>
public class Person {
public String displayName;
public int age;
boolean allowSorting = true;
public Person(String name, boolean allowSort) {
this.allowSorting = allowSort;
this.displayName = name;
}
}
// A class that implements Comparable and determines whether sorting should be done
// To be extended for each column
public abstract class SortWrapper implements Comparable {
public final Person theRealData;
public SortWrapper(Person theData) {
this.theRealData = theData;
}
@Override
public int compareTo(Object o) {
if (theRealData.allowSorting) {
return this.customCompare(o);
} else {
return 0;
}
}
protected abstract int customCompare(Object o);
@Override
public String toString() {
return super.toString();
}
}
// A sort helper for the Person's name
public class NameSortWrapper extends SortWrapper {
public NameSortWrapper(Person theData) {
super(theData);
}
@Override
protected int customCompare(Object o) {
return Collator.getInstance().compare(theRealData.displayName, ((NameSortWrapper) o).theRealData.displayName);
}
@Override
public String toString() {
if (theRealData == null) {
return null;
} else {
return theRealData.displayName;
}
}
}
// A sort helper for the Person's age
public class AgeSortWrapper extends SortWrapper {
public AgeSortWrapper(Person theData) {
super(theData);
}
@Override
protected int customCompare(Object o) {
return Integer.compare(theRealData.age, ((AgeSortWrapper) o).theRealData.age);
}
@Override
public String toString() {
// Note that null is returned when !allowSorting, this makes sure
// that the rows do not show an age for the categories
if (theRealData == null || !theRealData.allowSorting) {
return null;
} else {
return Integer.toString(theRealData.age);
}
}
}
public static void main(String[] args) {
launch(args);
}
}