如何设置TableColumn的排序箭头对齐?

时间:2018-03-06 00:21:37

标签: javafx alignment tablecolumn javafx-css

The Gist

在JavaFX TableColumn中,右侧有一个排序箭头。

Example of the mentioned arrow

如何设置此箭头的对齐?

我的用例

我问,因为我正在尝试将Material Design应用于JavaFX,箭头需要位于左侧 - 否则箭头似乎属于相邻列。

Arrow appearing to belong to the adjacent column.

做什么知道

我知道你可以这样TableColumnHeader

for (final Node headerNode : tableView.lookupAll(".column-header")) {
    TableColumnHeader tableColumnHeader = (TableColumnHeader) headerNode;

我知道TableColumnHeader有一个Label label和一个GridPane sortArrowGrid作为其子女。

如何将sortArrowGrid移到孩子的前面? .toFront()只适用于z顺序吗?

    Node arrow = tableColumnHeader.lookup(".arrow");
    if (arrow != null) {
        GridPane sortArrowGrid = (GridPane) arrow.getParent();
        // TODO: Move the sortArrowGrid to the front of the tableColumnHeader's children
    }

我觉得我可能正在犯这个错误 - 我希望用CSS来做。

1 个答案:

答案 0 :(得分:1)

在我的评论中扩展一点(带有一些代码):如前所述,排序指示器的对齐(或在fx-speak:内容显示中)是不可配置的,不是样式,也不是列/标题的任何属性 - 相反,它在标题的布局代码中进行了硬编码。

这意味着我们需要实现支持可配置显示的自定义columnHeader。肉是一个自定义的TableColumnHeader,它有:

  • 属性sortIconDisplayProperty(),用于配置排序指示符的相对位置
  • 被覆盖的layoutChildren(),用于将标签和排序指标定位为已配置
  • for fun:使该属性可以设置样式(需要在StyleableProperty周围使用一些样板并使用CSS处理程序注册)

要使用,我们需要自定义TableViewSkin,TableHeaderRow,NestedTableColumnHeader的整个堆栈:所有这些只是用于在相关工厂方法中创建和返回自定义xx实例的样板。

下面是一个粗略的例子(读:布局不完美,应该有一些填充并保证不与文本重叠......但是,核心并不是那么擅长它,也不支持)设置文本左侧的图标。为了获得完整的支持,您可能希望在顶部/底部实现设置..我现在太懒了;)

/**
 * https://stackoverflow.com/q/49121560/203657
 * position sort indicator at leading edge of column header
 * 
 * @author Jeanette Winzenburg, Berlin
 */
public class TableHeaderLeadingSortArrow extends Application {

    /**
     * Custom TableColumnHeader that lays out the sort icon at its leading edge.
     */
    public static class MyTableColumnHeader extends TableColumnHeader {

        public MyTableColumnHeader(TableColumnBase column) {
            super(column);
        }

        @Override
        protected void layoutChildren() {
            // call super to ensure that all children are created and installed
            super.layoutChildren();
            Node sortArrow = getSortArrow();
            // no sort indicator, nothing to do
            if (sortArrow == null || !sortArrow.isVisible()) return;
            if (getSortIconDisplay() == ContentDisplay.RIGHT) return;
            // re-arrange label and sort indicator
            double sortWidth = sortArrow.prefWidth(-1);
            double headerWidth = snapSizeX(getWidth()) - (snappedLeftInset() + snappedRightInset());
            double headerHeight = getHeight() - (snappedTopInset() + snappedBottomInset());

            // position sort indicator at leading edge
            sortArrow.resize(sortWidth, sortArrow.prefHeight(-1));
            positionInArea(sortArrow, snappedLeftInset(), snappedTopInset(),
                    sortWidth, headerHeight, 0, HPos.CENTER, VPos.CENTER);
            // resize label to fill remaining space
            getLabel().resizeRelocate(sortWidth, 0, headerWidth - sortWidth, getHeight());
        }

        // --------------- make sort icon location styleable
        // use StyleablePropertyFactory to simplify styling-related code
        private static final StyleablePropertyFactory<MyTableColumnHeader> FACTORY = 
                new StyleablePropertyFactory<>(TableColumnHeader.getClassCssMetaData());

        // default value (strictly speaking: an implementation detail)
        // PENDING: what about RtoL orientation? Is it handled correctly in
        // core?
        private static final ContentDisplay DEFAULT_SORT_ICON_DISPLAY = ContentDisplay.RIGHT;

        private static CssMetaData<MyTableColumnHeader, ContentDisplay> CSS_SORT_ICON_DISPLAY = 
                FACTORY.createEnumCssMetaData(ContentDisplay.class,
                        "-fx-sort-icon-display",
                        header -> header.sortIconDisplayProperty(),
                        DEFAULT_SORT_ICON_DISPLAY);

        // property with lazy instantiation
        private StyleableObjectProperty<ContentDisplay> sortIconDisplay;

        protected StyleableObjectProperty<ContentDisplay> sortIconDisplayProperty() {
            if (sortIconDisplay == null) {
                sortIconDisplay = new SimpleStyleableObjectProperty<>(
                        CSS_SORT_ICON_DISPLAY, this, "sortIconDisplay",
                        DEFAULT_SORT_ICON_DISPLAY);

            }
            return sortIconDisplay;
        }

        protected ContentDisplay getSortIconDisplay() {
            return sortIconDisplay != null ? sortIconDisplay.get()
                    : DEFAULT_SORT_ICON_DISPLAY;
        }

        protected void setSortIconDisplay(ContentDisplay display) {
            sortIconDisplayProperty().set(display);
        }

        /**
         * Returnst the CssMetaData associated with this class, which may
         * include the CssMetaData of its superclasses.
         * 
         * @return the CssMetaData associated with this class, which may include
         *         the CssMetaData of its superclasses
         */
        public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
            return FACTORY.getCssMetaData();
        }

        /** {@inheritDoc} */
        @Override
        public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
            return getClassCssMetaData();
        }

//-------- reflection acrobatics .. might use lookup and/or keeping aliases around
        private Node getSortArrow() {
            return (Node) FXUtils.invokeGetFieldValue(TableColumnHeader.class, this, "sortArrow");
        }

        private Label getLabel() {
            return (Label) FXUtils.invokeGetFieldValue(TableColumnHeader.class, this, "label");
        }

    }

    private Parent createContent() {
        // instantiate the tableView with the custom default skin
        TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(
                Locale.getAvailableLocales())) {

                    @Override
                    protected Skin<?> createDefaultSkin() {
                        return new MyTableViewSkin<>(this);
                    }

        };
        TableColumn<Locale, String> countryCode = new TableColumn<>("CountryCode");
        countryCode.setCellValueFactory(new PropertyValueFactory<>("country"));
        TableColumn<Locale, String> language = new TableColumn<>("Language");
        language.setCellValueFactory(new PropertyValueFactory<>("language"));
        TableColumn<Locale, String> variant = new TableColumn<>("Variant");
        variant.setCellValueFactory(new PropertyValueFactory<>("variant"));
        table.getColumns().addAll(countryCode, language, variant);

        BorderPane pane = new BorderPane(table);

        return pane;
    }

    /**
     * Custom nested columnHeader, headerRow und skin only needed to 
     * inject the custom columnHeader in their factory methods.
     */
    public static class MyNestedTableColumnHeader extends NestedTableColumnHeader {

        public MyNestedTableColumnHeader(TableColumnBase column) {
            super(column);
        }

        @Override
        protected TableColumnHeader createTableColumnHeader(
                TableColumnBase col) {
            return col == null || col.getColumns().isEmpty() || col == getTableColumn() ?
                    new MyTableColumnHeader(col) :
                    new MyNestedTableColumnHeader(col);
        }
    }

    public static class MyTableHeaderRow extends TableHeaderRow {

        public MyTableHeaderRow(TableViewSkinBase tableSkin) {
            super(tableSkin);
        }

        @Override
        protected NestedTableColumnHeader createRootHeader() {
            return new MyNestedTableColumnHeader(null);
        }
    }

    public static class MyTableViewSkin<T> extends TableViewSkin<T> {

        public MyTableViewSkin(TableView<T> table) {
            super(table);
        }

        @Override
        protected TableHeaderRow createTableHeaderRow() {
            return new MyTableHeaderRow(this);
        }

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        URL uri = getClass().getResource("columnheader.css");
        stage.getScene().getStylesheets().add(uri.toExternalForm());
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TableHeaderLeadingSortArrow.class.getName());

}

要配置的columnheader.css:

.column-header {
    -fx-sort-icon-display: LEFT;
}

版本说明

该示例针对fx9编码 - 它将Skins移动到公共范围以及其他一些更改。使其适用于fx8

  • 将import语句调整为com.sun中的旧位置。**(无论如何,您的IDE是您的朋友,未显示;)
  • 对于所有SomethingHeader,更改构造函数以包含tableSkin作为参数并在所有工厂方法中传递外观(可能在fx8中,如getTableViewSkin() - 或类似 - 具有受保护的范围,因此可供子类访问)< / LI>