3

要旨

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

上述箭头示例

如何设置此箭头的对齐方式?

我的用例

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

箭头似乎属于相邻列。

知道的

我知道你可以TableColumnHeader这样:

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

我知道它TableColumnHeader有 aLabel label和 aGridPane sortArrowGrid作为它的孩子。

我如何将它移到sortArrowGrid孩子们的前面?.toFront()仅适用于 z-order 对吗?

    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 来做。

4

1 回答 1

2

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

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

  • sortIconDisplayProperty()用于配置排序指示器的相对位置的属性
  • layoutChildren()按照配置定位标签和排序指示器的覆盖
  • 为了好玩:使该属性可样式化(需要一些围绕 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 一起使用

  • 将导入语句调整到 com.sun.** 中的旧位置(无论如何都没有显示,您的 IDE 是您的朋友;)
  • 对于所有 SomethingHeader,更改构造函数以包含 tableSkin 作为参数并在所有工厂方法中传递皮肤(可能在 fx8 中,因为getTableViewSkin()- 或类似 - 具有受保护的范围,因此子类可以访问)
于 2018-03-07T12:15:44.197 回答