如何仅在TreeTableView中按叶子排序?

时间:2015-09-07 20:26:21

标签: java sorting javafx

我在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更新中添加支持?

1 个答案:

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