JavaFX:如何移动“下拉箭头”在TitledPane中位于右侧 [英] JavaFX: How to move "drop down arrow" in TitledPane to be on right

查看:113
本文介绍了JavaFX:如何移动“下拉箭头”在TitledPane中位于右侧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望每个人都过得不错。

I hope everyone is doing well.

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

I'm trying to move the drop down arrow in a TitledPane to be laid out on the right, instead of the left like it is by default. I'm using JavaFX 8, and many of the resources I've found don't seem to work.

我发现我可以移动箭头a具体数量,例如下面显示的20个像素

I have found that I am able to move the arrow a specific amount, like 20 pixels shown below

.accordion .title > .arrow-button .arrow
{
    -fx-translate-x: 20;
}

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

But I want something responsive. Is there some way that I can get the width of the titled pane, and then subtract some pixels so that so that the arrow appears to be laid out on the right when resizing? Is there a better way to it? I added the element using SceneBuilder2 if that matters.

非常感谢您的时间。

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

The following was added for clarification


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

Primarily, I want the arrow to be right justified, like below


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

Instead of just "to the right" of the arrow. I really appreciate all the assistance.

推荐答案

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

Unfortunately, there's no public API for moving the arrow to the right side of the TitledPane. This doesn't mean this can't be accomplished, however, we just have to translate the arrow dynamically, using bindings. In order for the rest of the title area to look correct we'll also have to translate the text, and graphic if present, to the left. The easiest way to do all this is by subclassing TitledPaneSkin and accessing the internals of the "title region".

这是一个示例实现。它使您可以通过CSS将箭头定位在左侧或右侧。

Here's an example implementation. It lets you position the arrow on the left or right side via CSS. It's also responsive to resizing as well as alignment and graphic changes.

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

    }

}
You can then apply this skin to all TitledPanes in your application from CSS, like so:

然后您可以从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; }

Or you could target only certain TitledPanes by adding a style class and using said class instead of .titled-pane.

或者您也可以仅定位某些 TitledPane ,通过添加样式类并使用所述类代替 .titled-pane

The above works with JavaFX 11 and likely JavaFX 10 and 9 as well. To get it to compile on JavaFX 8 you need to change some things:

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

  • Import com.sun.javafx.scene.control.skin.TitledPaneSkin instead.


  • Import 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(); } }


  • Use new SimpleStyleableObjectProperty<ArrowSide>(...) {...} and new CssMetaData<TitledPane, ArrowSide>(...) {...}.


  • 使用 new SimpleStyleableObjectProperty< ArrowSide>(...){...} new CssMetaData< TitledPane,ArrowSide>(...){...}


    • 在Java的更高版本中类型推断得到改善。

    使用(StyleConverter<?,ArrowSide>)getEnumConverter(ArrowSide.class)


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

    Issue: Even with the above changes there's a problem in JavaFX 8—the arrow is only translated once the TitledPane is focused. This doesn't appear to be a problem with the above code as even changing the alignment property does not cause the TitledPane to update until it has focus (even when not using the above skin, but rather just the default skin). I've been unable to find a workaround to this problem (while using the custom skin) but maybe you or someone else can. I was using Java 1.8.0_202 when testing for JavaFX 8.

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

    If you don't want to use a custom skin, or you're on JavaFX 8 (this will cause the arrow to be translated without needing to focus the TitledPane first), you can extract the necessary code, with some modifications, into a utility method:

    如果您不想使用自定义外观,或者您使用的是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); } }

    Note: While this puts the arrow on the right without having to focus the TitledPane first, the TitledPane still suffers from the issue noted above. For instance, changing the alignment property doesn't update the TitledPane until it's focused. I'm guessing this is simply a bug in JavaFX 8.

    注意:虽然这将箭头无需先关注 TitledPane 的权利, TitledPane 仍然遭受上述问题的困扰。例如,更改 alignment 属性在将焦点对准之前不会更新 TitledPane 。我猜这只是JavaFX 8中的错误。

    This way of doing things is not as "easy" as the skin approach and requires two things:

    这种处理方式不像皮肤方法那样简单,需要两个事情:

    1. The TitledPane must be using the default TitledPaneSkin.
    2. The TitledPane must have been displayed in a Window (window was showing) before calling the utility method.


    1. TitledPane 必须使用默认的 TitledPaneSkin

    2. TitledPane 必须已显示在之前调用实用程序方法的 Window (窗口显示为 )。

    • Due to the lazy nature of JavaFX controls, the skin and the associated nodes will not have been created until the control has been displayed in a window. Calling the utility method before the control was displayed will result in a NullPointerException being thrown since the lookup calls will return null.
    • If using FXML, note that the initialize method is called during a call to FXMLLoader.load (any of the overloads). This means, under normal circumstances, it's not possible for the created nodes to be part of a Scene yet, let alone a showing Window. You must wait for the TitledPane to be displayed first, then call the utility method.


    • 由于JavaFX控件的惰性,因此直到将控件显示在窗口中之前,不会创建外观和关联的节点。在显示控件之前调用实用程序方法将导致抛出 NullPointerException ,因为 lookup 调用将返回 null

    • 如果使用FXML,请注意 initialize 方法是在调用 FXMLLoader.load (任何重载)期间调用。这意味着,在正常情况下,创建的节点不可能成为 Scene 的一部分,更不用说显示 Window 。您必须等待 TitledPane 首先显示,然后然后调用实用程序方法。

    Waiting for the TitledPane to be displayed can be achieved by listening to the Node.scene property, the Scene.window property, and the Window.showing property (or you could listen for WindowEvent.WINDOW_SHOWN events). However, if you immediately put the loaded nodes into a showing Window, then you can forgo observing the properties; call the utility method inside a Platform.runLater call from inside initialize.

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

    When using the skin approach, the whole wait-for-showing-window hassle is avoided.

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




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

    Usual Warning: This answer relies on the internal structure of TitledPane which may change in a future release. Be cautious when changing JavaFX versions. I only (somewhat) tested this on JavaFX 8u202 and JavaFX 11.0.2.

    这篇关于JavaFX:如何移动“下拉箭头”在TitledPane中位于右侧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆