首页 > 解决方案 > 将克隆对象添加到窗格时,线程“JavaFX 应用程序线程”java.lang.IndexOutOfBoundsException 中的异常

问题描述

我有以下类,我试图在其中实现原型模式:

    public class Element extends Group implements Cloneable{

    private final double ELEMENT_WIDTH = 50;
    private final double ELEMENT_HEIGHT = 70;

    private final double CIRCLE_RADIUS = 1;

    private int minimalNumberOfInputs;

    private Shape body = new Rectangle(ELEMENT_WIDTH, ELEMENT_HEIGHT, Color.WHITE);
    private Circle output = new Circle(CIRCLE_RADIUS);

    private Line outputLine = new Line(ELEMENT_WIDTH, ELEMENT_HEIGHT / 2, ELEMENT_WIDTH + 15, ELEMENT_HEIGHT / 2);

    private ArrayList<Circle> inputs;
    private ArrayList<Line> inputLines;
    private Circle inversionDesignation; 
    private Text symbol; 
    private Integer identifier;

    private double bodyCorX; 
    private double bodyCorY;

    private double corX = 0; 
    private double corY = 0;

    private double mouseX = 0; 
    private double mouseY = 0;

    private boolean dragging = false;
    
    public Integer getIdentifier() {
        return identifier;
    }

    public ArrayList<Circle> getInputs() {
        return inputs;
    }

    public Circle getOutput() {
        return output;
    }

    public Circle getInversionDesignation() {
        return inversionDesignation;
    }

    public double getELEMENT_WIDTH() {
        return ELEMENT_WIDTH;
    }

    public double getELEMENT_HEIGHT() {
        return ELEMENT_HEIGHT;
    }

    public double getCIRCLE_RADIUS() {
        return CIRCLE_RADIUS;
    }

    public int getMinimalNumberOfInputs() {
        return minimalNumberOfInputs;
    }

    public Shape getBody() {
        return body;
    }

    public Line getOutputLine() {
        return outputLine;
    }

    public ArrayList<Line> getInputLines() {
        return inputLines;
    }

    public Text getSymbol() {
        return symbol;
    }

    public double getBodyCorX() {
        return bodyCorX;
    }

    public double getBodyCorY() {
        return bodyCorY;
    }

    public double getCorX() {
        return corX;
    }

    public double getCorY() {
        return corY;
    }

    public double getMouseX() {
        return mouseX;
    }

    public double getMouseY() {
        return mouseY;
    }

    public boolean isDragging() {
        return dragging;
    }
 
    public Element(){
        
    }
    
    public Element(Circle inversionDesignation, Text symbol, int minimalNumberOfInputs) {
        this.minimalNumberOfInputs = minimalNumberOfInputs;
        this.body.setStroke(Color.BLACK);
        this.body.setStrokeType(StrokeType.INSIDE);
        this.body.setStrokeWidth(2.5);
        this.output.setFill(Color.BLACK);
        this.output.toFront();
        this.inversionDesignation = inversionDesignation;
        this.symbol = symbol;
        this.inputs = new ArrayList<>();
        this.inputLines = new ArrayList<>();
        this.identifier = this.hashCode();
        this.outputLine.setStrokeWidth(2);
        this.createStartInputs();
        this.configureInputPoints();
        this.bindGraphicalElements();
        elementMovementEvents();
        elementEnteredEvents();
    }

    private void bindGraphicalElements() {
        this.getChildren().add(body);
        this.getChildren().add(output);
        this.getChildren().add(outputLine);
        this.getChildren().addAll(inputs);
        this.getChildren().addAll(inputLines);

        if (this.symbol != null) {
            this.getChildren().add(symbol);
            symbol.relocate((ELEMENT_WIDTH / 2) - symbol.getTabSize() / 2, ELEMENT_HEIGHT / 8);
            symbol.setFont(new Font("Consolas", 14));
        }
        if (this.inversionDesignation != null) {
            this.getChildren().add(inversionDesignation);
            this.inversionDesignation.setStrokeType(StrokeType.INSIDE);
            this.inversionDesignation.setStrokeWidth(1);
            this.inversionDesignation.setStroke(Color.BLACK);
            this.inversionDesignation.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (this.inversionDesignation.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - this.inversionDesignation.getRadius());
            inversionDesignation.toFront();
        }
    }

    private void addGraphicalElement(Shape shape) {
        this.getChildren().add(shape);
    }

    private void createStartInputs() {
        for (int i = 0; i < this.minimalNumberOfInputs; i++) {
            inputs.add(new Circle(CIRCLE_RADIUS));
            inputs.get(i).setFill(Color.BLACK);
            inputs.get(i).toFront();
        }

        configureInputPoints();
        for (int i = 0; i < inputs.size(); i++) {
            Line line = new Line(inputs.get(i).getLayoutX() - 15, inputs.get(i).getLayoutY(), inputs.get(i).getLayoutX(), inputs.get(i).getLayoutY());
            line.setStrokeWidth(2);
            inputLines.add(line);
        }
    }

    private void addNewInput() {
        Circle newCircle = new Circle(CIRCLE_RADIUS);
        newCircle.setFill(Color.BLACK);
        this.inputs.add(newCircle);
        this.configureInputPoints();
        this.addGraphicalElement(newCircle);
    }

    private void configureInputPoints() {
        this.output.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (output.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - output.getRadius());
        int distance = (int) ELEMENT_HEIGHT / (inputs.size() + 1); //Растояние между точками входа.
        for (int i = 0; i < inputs.size(); i++) {
            inputs.get(i).relocate(this.bodyCorX - (CIRCLE_RADIUS - 1), this.bodyCorY + (distance * (i + 1) - CIRCLE_RADIUS));
        }
    }

    private void elementMovementEvents() {
        onMousePressedProperty().set(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                mouseX = event.getSceneX();
                mouseY = event.getSceneY();

                corX = getLayoutX();
                corY = getLayoutY();
            }
        });

        onMouseDraggedProperty().set(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                double offsetX = event.getSceneX() - mouseX; //смещение по X
                double offsetY = event.getSceneY() - mouseY;

                corX += offsetX;
                corY += offsetY;

                double scaledX = corX;
                double scaledY = corY;

                setLayoutX(scaledX);
                setLayoutY(scaledY);

                dragging = true;

                mouseX = event.getSceneX();
                mouseY = event.getSceneY();

                event.consume();
            }
        });

        onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                dragging = false;
            }
        });
    }

    private void testEvent() {
        this.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
            this.addNewInput();
        });
    }

    private void elementEnteredEvents() {
        onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if (event.getTarget() instanceof Circle) {
                    System.out.println("Circle!");
                }
            }
        });
    }
    
    @Override
    public Element clone() throws CloneNotSupportedException{
        return (Element)super.clone();
    }
    
    @Override
    public String toString() {
        return "Element " + this.hashCode() + ": location = " + this.getLayoutX() + ": output = " + this.minimalNumberOfInputs;
    }

    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Element)) return false;
        Element element = (Element) obj;
        return element.getBodyCorX() == bodyCorX && element.getBodyCorY() == bodyCorY &&  element.getBody() == body;
    }  
}

我正在尝试使用以下方法实现模式:

@Override
    public Element clone() throws CloneNotSupportedException{
        return (Element)super.clone();
    }

我有一个这样的控制器:

public class FXMLController {

    @FXML
    private AnchorPane anchorPane;
    @FXML
    private AnchorPane workPane;

    //prototypes
    private Element AndPrototype = new Element(null, new Text("&"), 2);
    private Element OrPrototype = new Element(null, new Text("1"), 2);
    private Element NotPrototype = new Element(new Circle(5, Color.WHITE), null, 1);
    private Element AndNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("&"), 2);
    private Element OrNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("1"), 2);

    @FXML
    public void initialize() {
    }

    @FXML
    private void method() throws CloneNotSupportedException {
        workPane.getChildren().add(AndPrototype.clone());
    }
}

在这种方法中,我正在尝试进行克隆并将其添加到 AnchorPane

@FXML
    private void method() throws CloneNotSupportedException {
        workPane.getChildren().add(AndPrototype.clone());
    }

结果,当我单击按钮时,我收到以下内容的错误:

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 8
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
    at java.base/java.util.Objects.checkIndex(Objects.java:359)
    at java.base/java.util.ArrayList.get(ArrayList.java:427)
    at javafx.base/com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
    at javafx.base/com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:305)
    at javafx.graphics/javafx.scene.Parent.updateCachedBounds(Parent.java:1704)
    at javafx.graphics/javafx.scene.Parent.recomputeBounds(Parent.java:1648)
    at javafx.graphics/javafx.scene.Parent.doComputeGeomBounds(Parent.java:1501)
    at javafx.graphics/javafx.scene.Parent$1.doComputeGeomBounds(Parent.java:115)
    at javafx.graphics/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl(ParentHelper.java:84)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeGeomBounds(NodeHelper.java:115)
    at javafx.graphics/javafx.scene.Node.updateGeomBounds(Node.java:3847)
    at javafx.graphics/javafx.scene.Node.getGeomBounds(Node.java:3809)
    at javafx.graphics/javafx.scene.Node.doComputeLayoutBounds(Node.java:3657)
    at javafx.graphics/javafx.scene.Node$1.doComputeLayoutBounds(Node.java:449)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBoundsImpl(NodeHelper.java:166)
    at javafx.graphics/com.sun.javafx.scene.GroupHelper.computeLayoutBoundsImpl(GroupHelper.java:63)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBounds(NodeHelper.java:106)
    at javafx.graphics/javafx.scene.Node$13.computeBounds(Node.java:3509)
    at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9782)
    at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9752)
    at javafx.graphics/javafx.scene.Node.getLayoutBounds(Node.java:3524)
    at javafx.graphics/javafx.scene.layout.AnchorPane.computeWidth(AnchorPane.java:272)
    at javafx.graphics/javafx.scene.layout.AnchorPane.computeMinWidth(AnchorPane.java:248)
    at javafx.graphics/javafx.scene.Parent.minWidth(Parent.java:1048)
    at javafx.graphics/javafx.scene.layout.Region.minWidth(Region.java:1553)
    at javafx.graphics/javafx.scene.layout.Region.computeChildPrefAreaWidth(Region.java:2012)
    at javafx.graphics/javafx.scene.layout.AnchorPane.computeChildWidth(AnchorPane.java:315)
    at javafx.graphics/javafx.scene.layout.AnchorPane.layoutChildren(AnchorPane.java:353)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1207)
    at javafx.graphics/javafx.scene.Scene.doLayoutPass(Scene.java:576)
    at javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2476)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:413)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:412)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:439)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:563)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:543)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:536)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:342)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    at java.base/java.lang.Thread.run(Thread.java:831)

我完全不知道这个错误可能与什么有关,以及它们向哪个方向移动。

标签: javafx

解决方案


通常这是因为您从 JavaFX 线程修改了场景图或场景图节点的属性。

类似的堆栈跟踪都是由线程错误引起的:

如果是多线程问题,通常可以通过删除不必要的线程来解决。或者,如果多线程是不可避免的,使用 javafx.concurrent 包或 Platform.runLater 等工具确保活动场景图中的节点仅在 JavaFX 线程上修改。

但是,如果您没有进行任何多线程,则可能是由于您正在进行的奇怪的克隆工作,这可能是不明智的。 JavaFX 节点只能在场景中出现一次,并且克隆可能会导致框架出现故障。


推荐阅读