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

如何将自定义可拖动Pane组件集成至SceneBuilder?

如何将自定义可拖动Pane集成到SceneBuilder

核心前提:自定义JavaFX组件被SceneBuilder识别的要求

SceneBuilder只能实例化符合JavaFX组件规范的类,核心要求包括:

  • 必须提供无参构造函数(SceneBuilder无法直接调用带参数的构造方法)
  • 组件属性需遵循JavaBean规范(提供getter/setter方法,可选配套Property属性)
  • 组件需打包为jar或确保SceneBuilder能访问到类文件

针对你的Drag类的调整

你的当前类只有带参数的构造器,这是SceneBuilder无法识别的主要原因。同时代码中存在两处逻辑小问题,以下是适配SceneBuilder的修改版本:

原始代码

import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.joml.Vector2i;

public class Drag extends StackPane {
    public Rectangle2D dragArea;
    public Region parent;
    public Point2D cursor;
    public Vector2i cursorOffset;
    public Drag(Region parent) {
        this.parent = parent;
        setDragArea();
        setOnMousePressed(event -> {
            if (event.getButton() != MouseButton.MIDDLE) {
                setDragArea();
                Point2D cursor = getNewCursor(event);
                setCursor(cursor);
                setCursorOffset();
                toFront();
            } else {
                ((Pane) this.getParent()).getChildren().remove(this);
            }
        });
        setOnMouseDragged(event -> {
            if (event.getButton() != MouseButton.MIDDLE) {
                Point2D cursor = getNewCursor(event);
                Vector2i velocity = getVelocity(cursor);
                if (Math.abs(getCursorOffset().x - getNewCursorOffset().x) < 10) {
                    int futureX = getLayoutX() + getWidth() + velocity.x > parent.getWidth() ? (int) ((int) parent.getWidth() - getWidth()) : (int) Math.max(0, (getLayoutX() + velocity.x));
                    setLayoutX(futureX);
                } else if (parent.contains(cursor) && Math.abs(getCursorOffset().x - getNewCursorOffset().x) < 10) {
                    setLayoutX(getLayoutX() - getCursorOffset().x);
                }
                if (Math.abs(getCursorOffset().y - getNewCursorOffset().y) < 10) {
                    int futureY = getLayoutY() + getHeight() + velocity.y > parent.getHeight() ? (int) ((int) parent.getHeight() - getHeight()) : (int) Math.max(0, (getLayoutY() + velocity.y));
                    setLayoutY(futureY);
                } else if (parent.contains(cursor) && Math.abs(getCursorOffset().y - getNewCursorOffset().y) < 10) {
                    setLayoutY(getLayoutY() - getCursorOffset().y);
                }
                setCursor(cursor);
            }
        });
    }

    public void setCursorOffset() {
        cursorOffset = getNewCursorOffset();
    }

    public Vector2i getCursorOffset() {
        return cursorOffset;
    }
    public Vector2i getNewCursorOffset() {
        return new Vector2i((int) ((cursor.getX() - getLayoutX())-parent.getLayoutX()), (int) (cursor.getY() - getLayoutY()-parent.getLayoutX()));
    }

    public Vector2i getVelocity(Point2D newCursor) {
        return new Vector2i((int) (newCursor.getX()-getCurrentCursor().getX()), (int) (newCursor.getY()-getCurrentCursor().getY()));
    }
    public void setDragArea() {
        dragArea = new Rectangle2D(parent.getLayoutX(), parent.getLayoutX(), parent.getWidth(), parent.getHeight());
    }
    public void setCursor(Point2D cursor) {
        this.cursor = cursor;
    }
    public Point2D getCurrentCursor() {
        return cursor;
    }
    public Point2D getNewCursor(MouseEvent event) {
        int x = (int) event.getSceneX();
        int y = (int) event.getSceneY();
        return new Point2D(x,y);
    }
}

修改后的适配代码

import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.joml.Vector2i;

// 改名更直观,可选
public class DragPane extends StackPane { 
    private Rectangle2D dragArea;
    private Region parentRegion; // 避免和getParent()方法命名冲突
    private Point2D cursor;
    private Vector2i cursorOffset;

    // SceneBuilder必须的无参构造
    public DragPane() {
        initMouseListeners();
    }

    // 保留带参构造,供代码中直接实例化使用
    public DragPane(Region parent) {
        this.parentRegion = parent;
        setDragArea();
        initMouseListeners();
    }

    // 抽离鼠标监听逻辑,代码更清晰
    private void initMouseListeners() {
        setOnMousePressed(event -> {
            if (event.getButton() != MouseButton.MIDDLE) {
                // 自动获取父容器(如果未手动设置)
                if (parentRegion == null && getParent() instanceof Region) {
                    parentRegion = (Region) getParent();
                }
                setDragArea();
                Point2D cursor = getNewCursor(event);
                setCursor(cursor);
                setCursorOffset();
                toFront();
            } else {
                if (getParent() instanceof Pane) {
                    ((Pane) getParent()).getChildren().remove(this);
                }
            }
        });

        setOnMouseDragged(event -> {
            if (event.getButton() != MouseButton.MIDDLE && parentRegion != null) {
                Point2D cursor = getNewCursor(event);
                Vector2i velocity = getVelocity(cursor);
                // 简化边界计算逻辑
                if (Math.abs(getCursorOffset().x - getNewCursorOffset().x) < 10) {
                    int futureX = (int) (getLayoutX() + velocity.x);
                    futureX = Math.max(0, Math.min(futureX, parentRegion.getWidth() - getWidth()));
                    setLayoutX(futureX);
                } else if (parentRegion.contains(cursor) && Math.abs(getCursorOffset().x - getNewCursorOffset().x) < 10) {
                    setLayoutX(getLayoutX() - getCursorOffset().x);
                }
                if (Math.abs(getCursorOffset().y - getNewCursorOffset().y) < 10) {
                    int futureY = (int) (getLayoutY() + velocity.y);
                    futureY = Math.max(0, Math.min(futureY, parentRegion.getHeight() - getHeight()));
                    setLayoutY(futureY);
                } else if (parentRegion.contains(cursor) && Math.abs(getCursorOffset().y - getNewCursorOffset().y) < 10) {
                    setLayoutY(getLayoutY() - getCursorOffset().y);
                }
                setCursor(cursor);
            }
        });
    }

    // 遵循JavaBean规范的getter/setter,供SceneBuilder配置属性
    public void setParentRegion(Region parentRegion) {
        this.parentRegion = parentRegion;
        setDragArea();
    }

    public Region getParentRegion() {
        return parentRegion;
    }

    public void setCursorOffset() {
        cursorOffset = getNewCursorOffset();
    }

    public Vector2i getCursorOffset() {
        return cursorOffset;
    }

    public Vector2i getNewCursorOffset() {
        if (cursor == null || parentRegion == null) return new Vector2i(0, 0);
        // 修复原代码中Y轴计算错误
        return new Vector2i(
                (int) (cursor.getX() - getLayoutX() - parentRegion.getLayoutX()),
                (int) (cursor.getY() - getLayoutY() - parentRegion.getLayoutY())
        );
    }

    public Vector2i getVelocity(Point2D newCursor) {
        if (cursor == null) return new Vector2i(0, 0);
        return new Vector2i(
                (int) (newCursor.getX() - cursor.getX()),
                (int) (newCursor.getY() - cursor.getY())
        );
    }

    public void setDragArea() {
        if (parentRegion != null) {
            // 修复原代码中Rectangle2D的Y轴起始坐标错误
            dragArea = new Rectangle2D(
                    parentRegion.getLayoutX(),
                    parentRegion.getLayoutY(),
                    parentRegion.getWidth(),
                    parentRegion.getHeight()
            );
        }
    }

    public void setCursor(Point2D cursor) {
        this.cursor = cursor;
    }

    public Point2D getCurrentCursor() {
        return cursor;
    }

    public Point2D getNewCursor(MouseEvent event) {
        return new Point2D(event.getSceneX(), event.getSceneY());
    }
}

集成到SceneBuilder的步骤

  1. 编译打包组件
    • 确保项目编译成功,包含所有依赖(比如joml库)。可以用IDE导出为jar文件,若依赖未打包进jar,需单独准备依赖jar。
  2. 导入到SceneBuilder
    • 打开SceneBuilder,点击顶部菜单栏Library -> Import JAR/FXML File。
    • 选择你的自定义组件jar,在弹窗中勾选DragPane(或你的类名),点击Import。
    • 若有依赖jar(如joml),重复此步骤导入。
  3. 使用自定义组件
    • 导入后,组件会出现在左侧Library面板的Custom分类下。
    • 拖动组件到场景中,在右侧Properties面板找到parentRegion属性,选择场景中的父容器(如AnchorPane)绑定。
  4. FXML验证
    • 自动生成的FXML会包含组件全路径,示例:
      <com.yourpackage.DragPane fx:id="dragPane" parentRegion="#mainAnchorPane" />
      

额外注意事项

  • 原代码中存在两处逻辑错误:setDragArea()里的Rectangle2D第二个参数误用了X坐标,getNewCursorOffset()里的Y轴计算同样误用了X坐标,已在修改后的代码中修复。
  • 若想让组件在SceneBuilder中支持子元素编辑,可以添加@DefaultProperty("children")注解(继承自StackPane,默认已支持,但显式添加更稳妥)。
  • 若SceneBuilder提示找不到类,检查jar是否正确导入,或类的全路径是否在FXML中正确填写。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 16:07:03