JavaFX:如何将 TitledPane 中的 "drop down arrow" 移动到右侧
JavaFX: How to move "drop down arrow" in TitledPane to be on right
希望大家一切顺利
我正在尝试将 TitledPane 中的下拉箭头移动到右侧,而不是默认情况下的左侧。我正在使用 JavaFX 8,我发现的许多资源似乎都不起作用。
我发现我可以将箭头移动特定的量,如下所示的 20 像素
.accordion .title > .arrow-button .arrow
{
-fx-translate-x: 20;
}
但我想要响应式的东西。有什么方法可以让我获得标题窗格的宽度,然后减去一些像素,以便在调整大小时箭头看起来位于右侧?有更好的方法吗?如果重要的话,我使用 SceneBuilder2 添加了元素。
非常感谢您的宝贵时间。
编辑:添加了以下内容以进行说明
首先,我希望箭头右对齐,如下所示
而不是只是 "to the right" 的箭头。非常感谢所有的帮助。
这在视觉上并不完全相同,但您可以隐藏箭头按钮并创建一个类似于箭头按钮的图形。 TitledPane 扩展了 Labeled,因此您可以通过 contentDisplay 属性.
控制图形相对于文本的位置
首先,隐藏样式表中的箭头按钮:
.accordion .title > .arrow-button
{
visibility: hidden;
}
在代码中,您可以创建一个Label来充当假按钮并将其设置为TitledPane的图形。整个标题行对鼠标敏感,因此不需要交互式控件(如按钮)。
Label collapseButton = new Label();
collapseButton.textProperty().bind(
Bindings.when(titledPane.expandedProperty())
.then("\u25bc").otherwise("\u25b6"));
titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);
不幸的是,没有 public API 可以将箭头移动到 TitledPane
的右侧。这并不意味着这不能完成,但是,我们只需要使用绑定动态转换箭头。为了使标题区域的其余部分看起来正确,我们还必须将文本和图形(如果存在)翻译到左侧。完成这一切的最简单方法是通过 subclassing TitledPaneSkin
并访问 "title region".
的内部结构
这是一个示例实现。它允许您通过 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;
}
或者您可以通过添加样式 class 并使用所述 class 而不是 .titled-pane
.
来仅定位某些 TitledPane
以上适用于 JavaFX 11,也可能适用于 JavaFX 10 和 9。要使其在 JavaFX 8 上编译,您需要更改一些内容:
改为导入 com.sun.javafx.scene.control.skin.TitledPaneSkin
。
- 皮肤 classes 是在 JavaFX 9 中制作的 public。
删除对 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 中的一个错误。
这种做事方式不像皮肤方法那样"easy",需要两件事:
TitledPane
必须使用默认值 TitledPaneSkin
。
TitledPane
必须已显示在Window
中(window显示 ) 在 调用实用程序方法之前。
- 由于 JavaFX 控件的惰性,在控件显示在 window 中之前不会创建皮肤和关联的节点。在显示控件之前调用实用程序方法将导致抛出
NullPointerException
,因为 lookup
调用将 return null
.
如果使用 FXML,请注意 initialize
方法是在调用 FXMLLoader.load
(任何重载)期间调用的。这意味着,在正常情况下,创建的节点不可能成为 Scene
的一部分,更不用说显示 Window
了。您必须先等待 TitledPane
显示,然后 然后 调用实用程序方法。
等待TitledPane
显示可以通过监听Node.scene
属性、Scene.window
属性、Window.showing
属性(或者你可以监听 WindowEvent.WINDOW_SHOWN
事件)。然而,如果你立即将加载的节点放入显示Window
,那么你可以放弃观察属性;从 initialize
.
内部的 Platform.runLater
调用中调用实用程序方法
使用皮肤方法时,整个 wait-for-showing-window 麻烦都避免了。
通常警告:此答案依赖于 TitledPane
的内部结构,这可能会在未来的版本中发生变化。更改 JavaFX 版本时要小心。我只(有点)在 JavaFX 8u202 和 JavaFX 11.0.2.
上测试了这个
在 FXML 中,您只需添加 nodeOrientation="RIGHT_TO_LEFT"
或使用 yourNode.setNodeOrientation ((NodeOrientation orientation)
https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/Node.html#setNodeOrientation(javafx.geometry.NodeOrientation)
希望大家一切顺利
我正在尝试将 TitledPane 中的下拉箭头移动到右侧,而不是默认情况下的左侧。我正在使用 JavaFX 8,我发现的许多资源似乎都不起作用。
我发现我可以将箭头移动特定的量,如下所示的 20 像素
.accordion .title > .arrow-button .arrow
{
-fx-translate-x: 20;
}
但我想要响应式的东西。有什么方法可以让我获得标题窗格的宽度,然后减去一些像素,以便在调整大小时箭头看起来位于右侧?有更好的方法吗?如果重要的话,我使用 SceneBuilder2 添加了元素。
非常感谢您的宝贵时间。
编辑:添加了以下内容以进行说明
首先,我希望箭头右对齐,如下所示
而不是只是 "to the right" 的箭头。非常感谢所有的帮助。
这在视觉上并不完全相同,但您可以隐藏箭头按钮并创建一个类似于箭头按钮的图形。 TitledPane 扩展了 Labeled,因此您可以通过 contentDisplay 属性.
控制图形相对于文本的位置首先,隐藏样式表中的箭头按钮:
.accordion .title > .arrow-button
{
visibility: hidden;
}
在代码中,您可以创建一个Label来充当假按钮并将其设置为TitledPane的图形。整个标题行对鼠标敏感,因此不需要交互式控件(如按钮)。
Label collapseButton = new Label();
collapseButton.textProperty().bind(
Bindings.when(titledPane.expandedProperty())
.then("\u25bc").otherwise("\u25b6"));
titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);
不幸的是,没有 public API 可以将箭头移动到 TitledPane
的右侧。这并不意味着这不能完成,但是,我们只需要使用绑定动态转换箭头。为了使标题区域的其余部分看起来正确,我们还必须将文本和图形(如果存在)翻译到左侧。完成这一切的最简单方法是通过 subclassing TitledPaneSkin
并访问 "title region".
这是一个示例实现。它允许您通过 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;
}
或者您可以通过添加样式 class 并使用所述 class 而不是 .titled-pane
.
TitledPane
以上适用于 JavaFX 11,也可能适用于 JavaFX 10 和 9。要使其在 JavaFX 8 上编译,您需要更改一些内容:
改为导入
com.sun.javafx.scene.control.skin.TitledPaneSkin
。- 皮肤 classes 是在 JavaFX 9 中制作的 public。
删除对
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.
如果您不想使用自定义皮肤,或者您使用的是 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 中的一个错误。
这种做事方式不像皮肤方法那样"easy",需要两件事:
TitledPane
必须使用默认值TitledPaneSkin
。TitledPane
必须已显示在Window
中(window显示 ) 在 调用实用程序方法之前。- 由于 JavaFX 控件的惰性,在控件显示在 window 中之前不会创建皮肤和关联的节点。在显示控件之前调用实用程序方法将导致抛出
NullPointerException
,因为lookup
调用将 returnnull
. 如果使用 FXML,请注意
initialize
方法是在调用FXMLLoader.load
(任何重载)期间调用的。这意味着,在正常情况下,创建的节点不可能成为Scene
的一部分,更不用说显示Window
了。您必须先等待TitledPane
显示,然后 然后 调用实用程序方法。等待
TitledPane
显示可以通过监听Node.scene
属性、Scene.window
属性、Window.showing
属性(或者你可以监听WindowEvent.WINDOW_SHOWN
事件)。然而,如果你立即将加载的节点放入显示Window
,那么你可以放弃观察属性;从initialize
. 内部的
Platform.runLater
调用中调用实用程序方法- 由于 JavaFX 控件的惰性,在控件显示在 window 中之前不会创建皮肤和关联的节点。在显示控件之前调用实用程序方法将导致抛出
使用皮肤方法时,整个 wait-for-showing-window 麻烦都避免了。
通常警告:此答案依赖于 TitledPane
的内部结构,这可能会在未来的版本中发生变化。更改 JavaFX 版本时要小心。我只(有点)在 JavaFX 8u202 和 JavaFX 11.0.2.
在 FXML 中,您只需添加 nodeOrientation="RIGHT_TO_LEFT" 或使用 yourNode.setNodeOrientation ((NodeOrientation orientation) https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/Node.html#setNodeOrientation(javafx.geometry.NodeOrientation)