当前位置: 首页 > 知识库问答 >
问题:

如何在JavaFX中删除窗格后调整手风琴大小

白智
2023-03-14

我正在做一个小的JavaFX项目。在我的一个场景中,我想动态地添加和移除我实现的自定义组件,它分别从TitledPane扩展到Accordion。这一切都很好,但从手风琴上移除窗格后,这该死的东西不会立即调整大小,但只有在我单击gui上的某个位置后。我准备了下面这张GIF,把问题可视化给你看。

有人能告诉我为什么手风琴只有在我点击gui界面上的某个地方后才会调整大小,而不是立即调整大小吗?我的意思是自动调整大小似乎不是问题,它只是不会触发...有人能告诉我为什么会这样吗?也许这是显而易见的,但我对JavaFX不是很熟悉,所以我真的被困在这里。我还观察到其他组件的类似行为,所以也许我在这里从根本上错过了一些东西。

更新

好的,我为你创建了一个最小的例子来重现我的问题。您可以在GitHub javafx-demo上克隆存储库,并亲自试用。这样做的时候,我注意到,只有当我点击它的时候,而不是当我点击gui上的任何地方的时候,手风琴才会调整大小。

更新1

我进一步简化了这个例子。您可以在上面的 GitHub 存储库中找到该示例,或查看下面的代码:

应用程序

public class App extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("parentView.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

家长控制器

public class ParentController {

    @FXML
    private Accordion accordion;

    public void onAddAction() {
        var itemControl = new ItemControl();
        EventHandler<ActionEvent> removeEventHandler = event -> {
            accordion.getPanes().remove(itemControl);
        };
        itemControl.setOnRemoveProperty(removeEventHandler);
        accordion.getPanes().add(itemControl);
    }
}

父视图

<StackPane xmlns="http://javafx.com/javafx/16"
           xmlns:fx="http://javafx.com/fxml/1"
           fx:controller="org.example.ParentController">
   <Group StackPane.alignment="CENTER">
      <VBox>
         <Accordion fx:id="accordion"/>
         <Button onAction="#onAddAction" text="Add"/>
      </VBox>
   </Group>
</StackPane>

项目控制

public class ItemControl extends TitledPane {

    private final UUID id = UUID.randomUUID();
    private final ObjectProperty<EventHandler<ActionEvent>> onRemoveProperty = new SimpleObjectProperty<>();

    @FXML
    private Button removeButton;

    public ItemControl() {
        FXMLLoader fxmlLoader = new FXMLLoader(ItemControl.class.getResource("itemControl.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @FXML
    public void initialize() {
        removeButton.onActionProperty().bind(onRemoveProperty);
    }

    public void setOnRemoveProperty(EventHandler<ActionEvent> onRemoveProperty) {
        this.onRemoveProperty.set(onRemoveProperty);
    }
    
    // equals and hashCode omitted for brevity (id instance variable is used as identifier)
}

ItemControl FXML

<fx:root type="javafx.scene.control.TitledPane" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
    <VBox>
        <Button fx:id="removeButton" text="Remove"/>
    </VBox>
</fx:root>

共有1个答案

荀金鹏
2023-03-14

这种行为是手风琴皮肤中的一个错误。技术原因是它保留了对当前和以前扩展窗格的内部引用——两者都用于计算min/pref高度——这些引用在删除扩展窗格时没有正确更新。修复方法是,如果窗格不再是手风琴的一部分,则将这些引用归零。f. i.从皮肤的侦听器到窗格列表。

没有干净的方法来解决这个问题,因为所有涉及的字段/方法都是私有的——但是,如果我们被允许弄脏,我们可以通过反射来破解错误。

基础知识:

  • 扩展手风琴皮肤,让我们的手风琴使用扩展版本
  • 在外观中,覆盖 computeMin/Pref/Height 以在返回 super 之前检查/修复引用
  • 检查:窗格应包含在可折叠面板的窗格中
  • 修复:如果不是,则将引用设置为 null

笔记:

  • 对内部字段的反射访问要求在运行时打开包。
  • 通常需要注意的是:调整/依赖实现内部结构高度依赖于版本,最终可能会崩溃。
  • FXUtils是我的本地反射实用程序类,您必须用自己的实现替换它

代码

public class SimpleLayoutAccordionOnRemove extends Application {

    /**
     * AccordionSkin that hacks the broken layout after remove of expanded pane.
     */
    public static class HackedAccordionSkin extends AccordionSkin {

        public HackedAccordionSkin(Accordion control) {
            super(control);
        }

        @Override
        protected double computeMinHeight(double width, double topInset, double rightInset,
                double bottomInset, double leftInset) {
            checkPaneFields();
            return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
        }

        @Override
        protected double computePrefHeight(double width, double topInset, double rightInset,
                double bottomInset, double leftInset) {
            checkPaneFields();
            return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
        }

        private void checkPaneFields() {
            checkPaneField("previousPane");
            checkPaneField("expandedPane");
        }

        /**
         * Check if the pane referenced by the field with the given name is contained
         * in the accordion's panes and sets it to null if not.
         */
        private void checkPaneField(String fieldName) {
            TitledPane prev = (TitledPane) FXUtils.invokeGetFieldValue(AccordionSkin.class, this, fieldName);
            if (!getSkinnable().getPanes().contains(prev)) {
                FXUtils.invokeSetFieldValue(AccordionSkin.class, this, fieldName, null);
            }
        }

    }

    private Parent createContent() {
        Accordion accordion = new Accordion() {

            @Override
            protected Skin<?> createDefaultSkin() {
                return new HackedAccordionSkin(this);
            }

        };

        Button add = new Button("add");
        add.setOnAction(e -> {
            addTitledPane(accordion);
        });
        VBox accBox = new VBox(accordion, add);

        StackPane content = new StackPane(new Group(accBox));
        return content;
    }

    int count;
    private void addTitledPane(Accordion accordion) {
        TitledPane pane = new TitledPane();
        pane.setText("Pane " + count++);
        Button remove = new Button("remove");
        remove.setOnAction(e -> {
            accordion.getPanes().remove(pane);
        });
        VBox box = new VBox(remove);
        pane.setContent(box);
        accordion.getPanes().add(pane);
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent(), 600, 400));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}
 类似资料:
  • 我正在尝试构建一个包含6个窗格(作为父级添加到GridPane布局中)的简单Java项目。我必须在开始时设置窗口大小,并通过参考根布局的宽度和高度,设法将它们均匀地拆分。 但我想要他们调整大小,因为我改变了窗口的大小(使用鼠标,现在他们得到固定的大小)。 下面是我的代码:

  • 问题内容: 我正在将Ajax手风琴用于多个窗格。在回发时,当前打开的窗格始终重置为第一个窗格。 有什么办法可以解决这个问题? 谢谢 抢。 问题答案: 我通过在查询字符串中传递当前窗格索引,然后将当前窗格设置回目标页面的Page_Load事件中的索引,来解决此问题。 谢谢, 抢。

  • 我有下面的代码,它设置了一个特定大小的窗口。它还从外部URL加载图像。

  • 我创建了一个由HBox、按钮和标签组成的数组。每次按下“添加”按钮,我都会设置:

  • 我正在寻找在Android中实现手风琴视图的方法。 我尝试过在 recyclerview 适配器中使用子视图和父视图,并根据用户操作扩展 recyclerview 项目内的隐藏内容。我使用自定义线性布局管理器来动态测量列表的高度并将其设置为高度。 任何帮助都可以接受。提前感谢。!

  • 手风琴效果其实就是通过JS改变元素的height,然后加上transition来让css动起来。 准备HTML结构 这里我们使用 dl , 因为 dt 刚好可以模拟标题, dd 模拟内容。 <div class="panel"> <dl> <dt>One Item</dt> <dd>One Item Content .</dd>