You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JavaFX的TitledPane中动态插入可拖拽条目?

在JavaFX的TitledPane中动态添加多个可拖拽条目

一、用容器组件承载多个条目

TitledPane的setContent()方法仅能接收单个Node对象,因此需要用容器组件(如VBox、ListView)包裹多个条目,再将容器传入setContent()。这里以VBox为例,它适合垂直排列条目,且支持动态添加元素。

示例代码:

// 创建TitledPane实例
TitledPane titledPane = new TitledPane();
titledPane.setText("可拖拽条目列表");

// 创建VBox作为内容容器,设置条目间距和内边距
VBox contentBox = new VBox(8);
contentBox.setPadding(new Insets(10));
titledPane.setContent(contentBox);

// 初始化添加3个条目
for (int i = 1; i <= 3; i++) {
    Label draggableLabel = createDraggableLabel("条目 " + i);
    contentBox.getChildren().add(draggableLabel);
}

// 动态新增条目的按钮
Button addBtn = new Button("添加新条目");
addBtn.setOnAction(e -> {
    int count = contentBox.getChildren().size() + 1;
    Label newLabel = createDraggableLabel("新条目 " + count);
    contentBox.getChildren().add(newLabel);
});

二、实现条目拖拽功能

要让条目支持拖拽,需给每个条目(如Label)绑定JavaFX的拖拽事件,核心依赖Dragboard类处理数据传递,需覆盖以下4个关键事件:

  1. setOnDragDetected:检测到拖拽开始时,初始化拖拽数据
  2. setOnDragOver:拖拽经过目标时,允许放置操作
  3. setOnDragDropped:拖拽完成时,处理数据放置逻辑
  4. setOnDragDone:拖拽结束后清理资源

封装的可拖拽Label创建方法:

private Label createDraggableLabel(String text) {
    Label label = new Label(text);
    label.setStyle("-fx-background-color: #f0f0f0; -fx-padding: 8; -fx-border-radius: 4;");
    label.setPrefWidth(200);

    // 拖拽开始事件:初始化拖拽数据
    label.setOnDragDetected(event -> {
        Dragboard db = label.startDragAndDrop(TransferMode.COPY_OR_MOVE);
        ClipboardContent content = new ClipboardContent();
        content.putString(label.getText());
        db.setContent(content);
        event.consume();
    });

    // 拖拽经过事件:允许放置操作
    label.setOnDragOver(event -> {
        if (event.getGestureSource() != label && event.getDragboard().hasString()) {
            event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
        }
        event.consume();
    });

    // 拖拽放置事件:处理数据替换逻辑
    label.setOnDragDropped(event -> {
        Dragboard db = event.getDragboard();
        boolean success = false;
        if (db.hasString()) {
            label.setText(db.getString());
            success = true;
        }
        event.setDropCompleted(success);
        event.consume();
    });

    // 拖拽结束事件:移动模式下移除原条目
    label.setOnDragDone(event -> {
        if (event.getTransferMode() == TransferMode.MOVE) {
            ((VBox)label.getParent()).getChildren().remove(label);
        }
        event.consume();
    });

    return label;
}

三、完整可运行示例

将上述代码整合为完整JavaFX应用:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.scene.input.*;
import javafx.stage.Stage;
import javafx.geometry.Insets;

public class DraggableTitledPaneDemo extends Application {
    @Override
    public void start(Stage primaryStage) {
        TitledPane titledPane = new TitledPane();
        titledPane.setText("可拖拽条目列表");

        VBox contentBox = new VBox(8);
        contentBox.setPadding(new Insets(10));
        titledPane.setContent(contentBox);

        // 添加初始条目
        for (int i = 1; i <= 3; i++) {
            contentBox.getChildren().add(createDraggableLabel("条目 " + i));
        }

        Button addBtn = new Button("添加新条目");
        addBtn.setOnAction(e -> {
            int count = contentBox.getChildren().size() + 1;
            contentBox.getChildren().add(createDraggableLabel("新条目 " + count));
        });

        VBox root = new VBox(10, titledPane, addBtn);
        root.setPadding(new Insets(20));
        Scene scene = new Scene(root, 300, 400);

        primaryStage.setTitle("TitledPane可拖拽条目示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Label createDraggableLabel(String text) {
        Label label = new Label(text);
        label.setStyle("-fx-background-color: #f0f0f0; -fx-padding: 8; -fx-border-radius: 4;");
        label.setPrefWidth(200);

        label.setOnDragDetected(event -> {
            Dragboard db = label.startDragAndDrop(TransferMode.COPY_OR_MOVE);
            ClipboardContent content = new ClipboardContent();
            content.putString(label.getText());
            db.setContent(content);
            event.consume();
        });

        label.setOnDragOver(event -> {
            if (event.getGestureSource() != label && event.getDragboard().hasString()) {
                event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
            }
            event.consume();
        });

        label.setOnDragDropped(event -> {
            Dragboard db = event.getDragboard();
            boolean success = false;
            if (db.hasString()) {
                label.setText(db.getString());
                success = true;
            }
            event.setDropCompleted(success);
            event.consume();
        });

        label.setOnDragDone(event -> {
            if (event.getTransferMode() == TransferMode.MOVE) {
                ((VBox)label.getParent()).getChildren().remove(label);
            }
            event.consume();
        });

        return label;
    }

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

补充说明

  • 若需要复杂结构的条目,可替换Label为HBox或自定义Pane,给容器绑定拖拽事件即可。
  • 条目数量较多时,推荐使用ListView替代VBox,它自带滚动功能,且可通过setCellFactory自定义可拖拽单元格,性能更优。

内容的提问来源于stack exchange,提问作者Runsva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:10:33