JavaFX:如何将标题窗格中的“下拉箭头”移动到右侧

JavaFX:如何将标题窗格中的“下拉箭头”移动到右侧,java,javafx,accordion,scenebuilder,Java,Javafx,Accordion,Scenebuilder,我希望每个人都做得很好 我试图将标题窗格中的下拉箭头移动到右侧,而不是默认情况下的左侧。我使用的是JavaFX8,我发现的许多资源似乎都不起作用 我发现我能够将箭头移动特定的量,如下图所示的20像素 .accordion .title > .arrow-button .arrow { -fx-translate-x: 20; } 但我想要一些有反应的东西。是否有某种方法可以获得标题窗格的宽度,然后减去一些像素,以便调整大小时箭头显示在右侧?有更好的方法吗?如果有必要,我使用Sce

我希望每个人都做得很好

我试图将标题窗格中的下拉箭头移动到右侧,而不是默认情况下的左侧。我使用的是JavaFX8,我发现的许多资源似乎都不起作用

我发现我能够将箭头移动特定的量,如下图所示的20像素

.accordion .title > .arrow-button .arrow
{
    -fx-translate-x: 20;
}
但我想要一些有反应的东西。是否有某种方法可以获得标题窗格的宽度,然后减去一些像素,以便调整大小时箭头显示在右侧?有更好的方法吗?如果有必要,我使用SceneBuilder2添加了元素

非常感谢你抽出时间

编辑:添加以下内容以进行澄清

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


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

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

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

.accordion .title > .arrow-button
{
    visibility: hidden;
}
在代码中,您可以创建一个标签作为假按钮,并将其设置为标题窗格的图形。整个标题行对鼠标很敏感,因此不需要像按钮这样的交互式控件

Label collapseButton = new Label();
collapseButton.textProperty().bind(
    Bindings.when(titledPane.expandedProperty())
        .then("\u25bc").otherwise("\u25b6"));

titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);

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

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

.accordion .title > .arrow-button
{
    visibility: hidden;
}
在代码中,您可以创建一个标签作为假按钮,并将其设置为标题窗格的图形。整个标题行对鼠标很敏感,因此不需要像按钮这样的交互式控件

Label collapseButton = new Label();
collapseButton.textProperty().bind(
    Bindings.when(titledPane.expandedProperty())
        .then("\u25bc").otherwise("\u25b6"));

titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);

不幸的是,没有用于将箭头移动到标题窗格右侧的公共API。这并不意味着这不能实现,但是,我们只需要使用绑定动态地转换箭头。为了使标题区域的其余部分看起来正确,我们还必须将文本和图形(如果有)翻译到左侧。实现这一切的最简单方法是将TitledPaneSkin子类化并访问title区域的内部

下面是一个示例实现。它允许您通过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);
        }

    }

}
或者,通过添加样式类并使用所述类而不是.titledPane,您可以只针对某些标题窗格

以上内容适用于JavaFX11,也可能适用于JavaFX10和9。要在JavaFX8上编译它,您需要更改一些内容:

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

皮肤类在JavaFX9中公开。 删除对registerChangeListener的调用。。。和未注册的更改侦听器。。。。我认为用以下内容替换它们是正确的:

@Override
protected void handleControlPropertyChange(String p) {
    super.handleControlPropertyChange(p);
    if ("GRAPHIC".equals(p)) {
        adjustTitleLayout();
    }
}
使用新的SimpleStyleableObjectProperty。。。{…}和新的CssMetaData。。。{…}

在以后的Java版本中,类型推断得到了改进。
使用StyleConverter不幸的是,没有用于将箭头移动到标题窗格右侧的公共API。这并不意味着这不能实现,但是,我们只需要使用绑定动态地转换箭头。为了使标题区域的其余部分看起来正确,我们还必须将文本和图形(如果有)翻译到左侧。实现这一切的最简单方法是将TitledPaneSkin子类化并访问title区域的内部

下面是一个示例实现。它允许您通过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);
        }

    }

}
或者,通过添加样式类并使用所述类而不是.titledPane,您可以只针对某些标题窗格

以上内容适用于JavaFX11,也可能适用于JavaFX10和9。要在JavaFX8上编译它,您需要更改一些内容:

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

皮肤类在JavaFX9中公开。 删除对registerChangeListener的调用。。。和未注册的更改侦听器。。。。我认为用以下内容替换它们是正确的:

@Override
protected void handleControlPropertyChange(String p) {
    super.handleControlPropertyChange(p);
    if ("GRAPHIC".equals(p)) {
        adjustTitleLayout();
    }
}
使用新的SimpleStyleableObjectProperty。。。{…}和新的CssMetaData。。。{…}

在以后的Java版本中,类型推断得到了改进。
在FXML中使用StyleConverter只需将nodeOrientation=RIGHT\u添加到\u LEFT即可 或者使用yourNode.setNodeOrientation​节点定向

在FXML中,您只需将nodeOrientation=RIGHT\u添加到\u LEFT即可 或者使用yourNode.setNodeOrientation​节点定向

您是只想要右边的箭头还是可以
文字也在右边?只有箭头。我希望文本左对齐。谢谢你对我的另一个问题感兴趣。你只想要右边的箭头还是文本也可以在右边?只有箭头。我希望文本左对齐。感谢您对我的另一个问题感兴趣。虽然此解决方案看起来很实用,但箭头仅显示在标题窗格文本的右侧,而不是标题窗格的右侧。有没有一种方法可以修改您在这里拥有的内容以获得这种效果?就像一个总是hgrows?@TerryDorsey的区域一样,它声称具有类标题的节点是一个HBox,所以是的,应该可以添加标签,而不是将其作为标题窗格图形传递……但是当我尝试时,我发现标题节点实际上是StackPane的一个子类。操作它或它的子节点可能会起作用,但在未来或过去版本的JavaFX中可能会很容易中断,因为无法保证子节点是什么。虽然此解决方案看起来功能正常,但箭头只显示在标题窗格文本的右侧,而不是标题窗格的右侧。有没有一种方法可以修改您在这里拥有的内容以获得这种效果?就像一个总是hgrows?@TerryDorsey的区域一样,它声称具有类标题的节点是一个HBox,所以是的,应该可以添加标签,而不是将其作为标题窗格图形传递……但是当我尝试时,我发现标题节点实际上是StackPane的一个子类。操纵它或它的子节点可能会奏效,但很容易在未来或过去版本的JavaFX中崩溃,因为无法保证子节点将是什么。当我以可控的方式测试此解决方案时,它在本地环境中完美地工作。然而,我尝试将其应用到我的项目中,其中唯一的区别是我使用SceneBuilder在FXML中定义了accordion,而不是在代码中实例化。当我把代码应用到手风琴上时,我会得到一个NPE:“Region arrow=Region title.lookup.arrow button;”你知道为什么会这样吗?提前谢谢。我在控制器中初始化只是为了添加细节。更新了答案以解决FXML。@TerryDorsey我更新了答案以提供更可靠的示例。然而,在它的核心,它仍然只是将箭头向右移动;属性的工厂方法通常是无用的,特别是当我们需要连接到invalidated时,但就我个人而言,我仍然让工厂创建和管理cssMetaData:这将样式表处理块减少到几行。当然,这只是我的偏好:当我以可控的方式测试这个解决方案时,它在我的本地环境中完美地工作。然而,我尝试将其应用到我的项目中,其中唯一的区别是我使用SceneBuilder在FXML中定义了accordion,而不是在代码中实例化。当我把代码应用到手风琴上时,我会得到一个NPE:“Region arrow=Region title.lookup.arrow button;”你知道为什么会这样吗?提前谢谢。我在控制器中初始化只是为了添加细节。更新了答案以解决FXML。@TerryDorsey我更新了答案以提供更可靠的示例。然而,在它的核心,它仍然只是将箭头向右移动;属性的工厂方法通常是无用的,特别是当我们需要连接到invalidated时,但就我个人而言,我仍然让工厂创建和管理cssMetaData:这将样式表处理块减少到几行。当然,我只喜欢: