JavaFX:如何将TitledPane中的“下拉箭头”移动到右侧

时间:2019-03-09 23:17:25

标签: java javafx accordion scenebuilder

我希望每个人都过得很好。

我试图将TitledPane中的下拉箭头移动到右侧,而不是默认情况下的左侧。我正在使用JavaFX 8,但是我发现的许多资源似乎都不起作用。

我发现我可以将箭头移动特定的量,例如下面显示的20像素

git rev-parse

但是我想要一些响应。有什么方法可以获取带标题的窗格的宽度,然后减去一些像素,以便在调整大小时箭头看起来像在右边一样?有更好的方法吗?如果重要的话,我使用SceneBuilder2添加了元素。

非常感谢您的时间。

编辑:为澄清起见添加了以下内容

enter image description here enter image description here

首先,我希望箭头右对齐,如下所示

enter image description here enter image description here

不仅仅是箭头的“右边”。我真的很感谢所有帮助。

3 个答案:

答案 0 :(得分:2)

从视觉上看,这并不完全相同,但是您可以隐藏箭头按钮并创建类似于箭头按钮的图形。 TitledPane扩展了Labeled,因此您可以通过contentDisplay属性来控制图形相对于文本的位置。

首先,在样式表中隐藏箭头按钮:

1: After 37: 
37

2: After 24: 
    24
37

3: After 30: 
    24
        30
37

4: After 36: 
    24
        30
            36
37

5: After 72: 
    24
        30
            36
37
    72

6: After 57: 
    24
        30
            36
37
        57
    72

7: After 32: 
    24
        30
                32
            36
37
        57
    72

8: After 62: 
    24
        30
                32
            36
37
        57
            62
    72

在代码中,您可以创建一个标签来充当假按钮,并将其设置为TitledPane的图形。整个标题行都对鼠标敏感,因此不需要交互式控件(如Button)。

.accordion .title > .arrow-button
{
    visibility: hidden;
}

答案 1 :(得分:2)

很遗憾,没有公共API可以将箭头移动到TitledPane的右侧。这并不意味着无法完成,但是,我们只需要使用绑定来动态翻译箭头即可。为了使标题区域的其余部分看起来正确,我们还必须将文本和图形(如果有的话)向左平移。完成所有这些操作的最简单方法是将TitledPaneSkin子类化并访问“标题区域”的内部。

这是一个示例实现。它使您可以通过CSS将箭头定位在左侧或右侧。它还对调整大小以及对齐和图形更改做出响应。

package com.example;

import static javafx.css.StyleConverter.getEnumConverter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TitledPane;
import javafx.scene.control.skin.TitledPaneSkin;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;

public class CustomTitledPaneSkin extends TitledPaneSkin {

    public enum ArrowSide {
        LEFT, RIGHT
    }

    /* ********************************************************
     *                                                        *
     * Properties                                             *
     *                                                        *
     **********************************************************/

    private final StyleableObjectProperty<ArrowSide> arrowSide
            = new SimpleStyleableObjectProperty<>(StyleableProperties.ARROW_SIDE, this, "arrowSide", ArrowSide.LEFT) {
        @Override protected void invalidated() {
            adjustTitleLayout();
        }
    };
    public final void setArrowSide(ArrowSide arrowSide) { this.arrowSide.set(arrowSide); }
    public final ArrowSide getArrowSide() { return arrowSide.get(); }
    public final ObjectProperty<ArrowSide> arrowSideProperty() { return arrowSide; }

    /* ********************************************************
     *                                                        *
     * Instance Fields                                        *
     *                                                        *
     **********************************************************/

    private final Region title;
    private final Region arrow;
    private final Text text;

    private DoubleBinding arrowTranslateBinding;
    private DoubleBinding textGraphicTranslateBinding;
    private Node graphic;

    /* ********************************************************
     *                                                        *
     * Constructors                                           *
     *                                                        *
     **********************************************************/

    public CustomTitledPaneSkin(TitledPane control) {
        super(control);
        title = (Region) Objects.requireNonNull(control.lookup(".title"));
        arrow = (Region) Objects.requireNonNull(title.lookup(".arrow-button"));
        text = (Text) Objects.requireNonNull(title.lookup(".text"));

        registerChangeListener(control.graphicProperty(), ov -> adjustTitleLayout());
    }

    /* ********************************************************
     *                                                        *
     * Skin Stuff                                             *
     *                                                        *
     **********************************************************/

    private void adjustTitleLayout() {
        clearBindings();
        if (getArrowSide() != ArrowSide.RIGHT) {
            // if arrow is on the left we don't need to translate anything
            return;
        }

        arrowTranslateBinding = Bindings.createDoubleBinding(() -> {
            double rightInset = title.getPadding().getRight();
            return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
        }, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty());
        arrow.translateXProperty().bind(arrowTranslateBinding);

        textGraphicTranslateBinding = Bindings.createDoubleBinding(() -> {
            switch (getSkinnable().getAlignment()) {
                case TOP_CENTER:
                case CENTER:
                case BOTTOM_CENTER:
                case BASELINE_CENTER:
                    return 0.0;
                default:
                    return -(arrow.getWidth());
            }
        }, getSkinnable().alignmentProperty(), arrow.widthProperty());
        text.translateXProperty().bind(textGraphicTranslateBinding);

        graphic = getSkinnable().getGraphic();
        if (graphic != null) {
            graphic.translateXProperty().bind(textGraphicTranslateBinding);
        }
    }

    private void clearBindings() {
        if (arrowTranslateBinding != null) {
            arrow.translateXProperty().unbind();
            arrow.setTranslateX(0);
            arrowTranslateBinding.dispose();
            arrowTranslateBinding = null;
        }
        if (textGraphicTranslateBinding != null) {
            text.translateXProperty().unbind();
            text.setTranslateX(0);
            if (graphic != null) {
                graphic.translateXProperty().unbind();
                graphic.setTranslateX(0);
                graphic = null;
            }
            textGraphicTranslateBinding.dispose();
            textGraphicTranslateBinding = null;
        }
    }

    @Override
    public void dispose() {
        clearBindings();
        unregisterChangeListeners(getSkinnable().graphicProperty());
        super.dispose();
    }

    /* ********************************************************
     *                                                        *
     * Stylesheet Handling                                    *
     *                                                        *
     **********************************************************/

    public static List<CssMetaData<?, ?>> getClassCssMetaData() {
        return StyleableProperties.CSS_META_DATA;
    }

    @Override
    public List<CssMetaData<?, ?>> getCssMetaData() {
        return getClassCssMetaData();
    }

    private static class StyleableProperties {

        private static final CssMetaData<TitledPane, ArrowSide> ARROW_SIDE
                = new CssMetaData<>("-fx-arrow-side", getEnumConverter(ArrowSide.class), ArrowSide.LEFT) {

            @Override
            public boolean isSettable(TitledPane styleable) {
                Property<?> prop = (Property<?>) getStyleableProperty(styleable);
                return prop != null && !prop.isBound();
            }

            @Override
            public StyleableProperty<ArrowSide> getStyleableProperty(TitledPane styleable) {
                Skin<?> skin = styleable.getSkin();
                if (skin instanceof CustomTitledPaneSkin) {
                    return ((CustomTitledPaneSkin) skin).arrowSide;
                }
                return null;
            }

        };

        private static final List<CssMetaData<?, ?>> CSS_META_DATA;

        static {
            List<CssMetaData<?,?>> list = new ArrayList<>(TitledPane.getClassCssMetaData().size() + 1);
            list.addAll(TitledPaneSkin.getClassCssMetaData());
            list.add(ARROW_SIDE);
            CSS_META_DATA = Collections.unmodifiableList(list);
        }

    }

}

然后您可以通过CSS将这种皮肤应用于应用程序中的所有TitledPane,就像这样:

.titled-pane {
    -fx-skin: "com.example.CustomTitledPaneSkin";
    -fx-arrow-side: right;
}

/*
 * The arrow button has some right padding that's added
 * by "modena.css". This simply puts the padding on the
 * left since the arrow is positioned on the right.
 */
.titled-pane > .title > .arrow-button {
    -fx-padding: 0.0em 0.0em 0.0em 0.583em;
}

或者您可以通过添加样式类并使用该类而不是TitledPane来仅定位某些.titled-pane

以上内容适用于JavaFX 11,也可能适用于JavaFX 10和9。要使其在JavaFX 8上编译,您需要更改一些内容:

  • 改为导入com.sun.javafx.scene.control.skin.TitledPaneSkin

    • 皮肤类在JavaFX 9中公开。
  • 删除对registerChangeListener(...)unregisterChangeListeners(...)的呼叫。我相信将其替换为以下内容是正确的:

    @Override
    protected void handleControlPropertyChange(String p) {
        super.handleControlPropertyChange(p);
        if ("GRAPHIC".equals(p)) {
            adjustTitleLayout();
        }
    }
    
  • 使用new SimpleStyleableObjectProperty<ArrowSide>(...) {...}new CssMetaData<TitledPane, ArrowSide>(...) {...}

    • 类型推断在Java的更高版本中得到了改进。
  • 使用(StyleConverter<?, ArrowSide>) getEnumConverter(ArrowSide.class)

    • getEnumConverter的通用签名中存在一个错误,该错误已在更高版本中修复。使用强制转换可以解决该问题。您可能希望@SuppressWarnings("unchecked")演员表。

问题:即使进行了上述更改,JavaFX 8仍然存在问题-仅在TitledPane聚焦后,箭头才被翻译。上面的代码似乎没有问题,因为即使更改alignment属性也不会导致TitledPane进行更新,直到它具有焦点为止(即使不使用上面的皮肤,而只是默认皮肤)。我一直无法找到解决此问题的方法(使用自定义皮肤时),但也许您或其他人可以。测试JavaFX 8时,我使用的是Java 1.8.0_202。


如果您不想使用自定义外观,或者您使用的是JavaFX 8(这将导致箭头的翻译而无需首先关注TitledPane),则可以提取必要的代码经过一些修改后,变成了实用程序方法:

public static void putArrowOnRight(TitledPane pane) {
    Region title = (Region) pane.lookup(".title");
    Region arrow = (Region) title.lookup(".arrow-button");
    Text text = (Text) title.lookup(".text");

    arrow.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
        double rightInset = title.getPadding().getRight();
        return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
    }, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty()));
    arrow.setStyle("-fx-padding: 0.0em 0.0em 0.0em 0.583em;");

    DoubleBinding textGraphicBinding = Bindings.createDoubleBinding(() -> {
        switch (pane.getAlignment()) {
            case TOP_CENTER:
            case CENTER:
            case BOTTOM_CENTER:
            case BASELINE_CENTER:
                return 0.0;
            default:
                return -(arrow.getWidth());
        }
    }, arrow.widthProperty(), pane.alignmentProperty());
    text.translateXProperty().bind(textGraphicBinding);

    pane.graphicProperty().addListener((observable, oldGraphic, newGraphic) -> {
        if (oldGraphic != null) {
            oldGraphic.translateXProperty().unbind();
            oldGraphic.setTranslateX(0);
        }
        if (newGraphic != null) {
            newGraphic.translateXProperty().bind(textGraphicBinding);
        }
    });
    if (pane.getGraphic() != null) {
        pane.getGraphic().translateXProperty().bind(textGraphicBinding);
    }
}

注意:尽管这将箭头放到右边而不必首先集中TitledPane,但是TitledPane仍然受到上述问题的困扰。例如,更改alignment属性只有在重点关注之前,不会更新TitledPane。我猜这只是JavaFX 8中的错误。

这种做事方式不像皮肤方法那样“简单”,需要两件事:

  1. TitledPane必须使用默认的TitledPaneSkin
  2. TitledPane 必须已显示在Window中(窗口显示为 之前调用实用程序方法。

    • 由于JavaFX控件的惰性,在将控件显示在窗口中之前,不会创建外观和关联的节点。在显示控件之前调用实用程序方法将导致抛出NullPointerException,因为lookup调用将返回null
    • 如果使用FXML,请注意,在调用initialize(任何重载)期间,都会调用FXMLLoader.load方法。这意味着,在正常情况下,创建的节点不可能成为Scene的一部分,更不用说显示Window了。您必须等待TitledPane首先显示,然后然后调用实用程序方法。

      等待TitledPane的显示可以通过侦听Node.scene属性,Scene.window属性和Window.showing属性来实现(或者您可以听WindowEvent.WINDOW_SHOWN个事件)。但是,如果您立即将已加载的节点放入显示的Window 中,则可以放弃观察属性;在Platform.runLater内部的initialize调用中调用实用程序方法。

使用皮肤方法时,避免了整个“等待显示窗口”麻烦。


通常的警告:此答案取决于TitledPane的内部结构,在将来的发行版中可能会更改。更改JavaFX版本时要小心。我只是(某种程度上)在JavaFX 8u202和JavaFX 11.0.2上进行了测试。

答案 2 :(得分:0)

在FXML中,您只需添加nodeOrientation =“ RIGHT_TO_LEFT” 或使用yourNode.setNodeOrientation(((NodeOrientation方向) https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/Node.html#setNodeOrientation(javafx.geometry.NodeOrientation