如何撤消Vaadin画布上的上一个绘制框?

时间:2018-12-12 03:15:59

标签: java arrays canvas vaadin

我有一个画布,可以在Vaadin中的画布上绘制边界框,并且我正在尝试实现一个撤消功能,以便最后一个绘制的框都将从数组和画布上移除。在undoLast方法(可在Canvas.java中找到并在MainLayout.java中调用)中,我删除了数组的最后一个元素(包含绘制的所有框的arrayBoxes),并尝试在重新绘制画布之前清除整个画布。留在数组中。但是,我收到一个数组错误。

MainLayout:

package com.vaadin.starter.beveragebuddy.backend;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.lumo.Lumo;
import com.vaadin.starter.beveragebuddy.ui.components.BoundingBox;
import com.vaadin.starter.beveragebuddy.ui.components.Canvas;
import com.vaadin.starter.beveragebuddy.ui.components.CanvasRenderingContext2D;
import com.vaadin.starter.beveragebuddy.ui.components.MousePosition;
import com.vaadin.flow.component.textfield.TextField;
import java.util.ArrayList;

/**
 * The main layout contains the header with the navigation buttons, and the
 * child views below that.
 */

@HtmlImport("frontend://styles/shared-styles.html")
@Route("")
@Theme(Lumo.class)
public class MainLayout extends VerticalLayout {

    private CanvasRenderingContext2D ctx;
    private Canvas canvas;
    ArrayList<MousePosition> mousePosArray = Canvas.getMousePosArray();
    ArrayList<BoundingBox> bb = Canvas.getArrayBoxes();
    public static int count = 0;


    public MainLayout() {

        VerticalLayout footerLayout = new VerticalLayout();

        H2 title = new H2("Annotation UI");
        title.addClassName("main-layout__title");

        canvas = new Canvas(1580, 700);

        ctx = canvas.getContext();

        Div buttons = new Div();
        buttons.add(new NativeButton("Save Annotations"));
        buttons.add(new NativeButton("Previous Picture"));
        buttons.add(new NativeButton("Next Picture"));
        buttons.add(new NativeButton("Undo", e -> Canvas.undoLast()));
        buttons.add(new NativeButton("Clear Canvas",
                e -> ctx.clearRect(0, 0, 1580, 700)));

        add(canvas, buttons);

        Label label = new Label();
        canvas.addComponent(label);
        add(label);

//        HorizontalLayout fieldLayout = new HorizontalLayout();

        TextField boxname = new TextField();
        boxname.setLabel("Box Name:");
        boxname.setPlaceholder("Enter bounding box name");

        TextField boxcategory = new TextField();
        boxcategory.setLabel("Box Category:");
        boxcategory.setPlaceholder("Enter bounding box category");
        add(boxname, boxcategory);

        Button submitButton = new Button("Submit");
        submitButton.addClickListener(event -> {
            if (count == 0){
                count = count + 1;
            }
            bb.get(count - 1).setName(boxname.getValue());
            bb.get(count - 1).setBoxcategory(boxcategory.getValue());
            count = count + 1;
            boxname.clear();
            boxcategory.clear();

                System.out.println(bb.toString());


        });
        add(submitButton);

        canvas.addMouseMoveListener(() -> label.setText("Coordinates: " + mousePosArray.get(0)));

        }
    }

Canvas.java:

package com.vaadin.starter.beveragebuddy.ui.components;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.shared.Registration;
import elemental.json.JsonObject;
import java.util.ArrayList;
import java.util.List;

/**
 * Canvas component that you can draw shapes and images on. It's a Java wrapper
 * for the
 * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
 * canvas</a>.
 * <p>
 * Use {@link #getContext()} to get API for rendering shapes and images on the
 * canvas.
 * <p>
 */
@Tag("canvas")
@SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {

    private static CanvasRenderingContext2D context;
    private Element element;
    private boolean isDrawing = false;
    private boolean mouseIsDown = false;
    private double endX;
    private double endY;

    public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
    public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
    private List<Runnable> mouseMoveListeners = new ArrayList<>(0);

    public static ArrayList<BoundingBox> getArrayBoxes() {
        return arrayBoxes;
    }

    public static ArrayList<MousePosition> getMousePosArray() {
        return mousePosArray;
    }

    public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
        Canvas.mousePosArray = mousePosArray;
    }

    /**
     * Creates a new canvas component with the given size.
     * <p>
     * Use the API provided by {@link #getContext()} to render graphics on the
     * canvas.
     * <p>
     * The width and height parameters will be used for the canvas' coordinate
     * system. They will determine the size of the component in pixels, unless
     * you explicitly set the component's size with {@link #setWidth(String)} or
     * {@link #setHeight(String)}.
     *
//     * @param width
//     *            the width of the canvas
//     * @param height
//     *            the height of the canvas
//     */

    public Registration addMouseMoveListener(Runnable listener) {
        mouseMoveListeners.add(listener);
        return () -> mouseMoveListeners.remove(listener);
    }

    public Canvas(int width, int height) {

        context = new CanvasRenderingContext2D(this);

        element = getElement();
        element.getStyle().set("border", "1px solid");

        getElement().setAttribute("width", String.valueOf(width));
        getElement().setAttribute("height", String.valueOf(height));

        element.addEventListener("mousedown", event -> {  // Retrieve Starting Position on MouseDown

            Element boundingBoxResult = ElementFactory.createDiv();
            element.appendChild(boundingBoxResult);

            JsonObject evtData = event.getEventData();

            double xBox = evtData.getNumber("event.x");
            double yBox = evtData.getNumber("event.y");
            boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
            boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));

            BoundingBox newBox = new BoundingBox("","", xBox, yBox, 0.0, 0.0);
            arrayBoxes.add(newBox);

            isDrawing = true;
            mouseIsDown=true;

            mouseMoveListeners.forEach(Runnable::run);

        }).addEventData("event.x").addEventData("event.y");


        element.addEventListener("mouseup", event -> {  // Draw Box on MouseUp

            Element boundingBoxResult2 = ElementFactory.createDiv();
            element.appendChild(boundingBoxResult2);

            JsonObject evtData2 = event.getEventData();

            endX = evtData2.getNumber("event.x");
            endY = evtData2.getNumber("event.y");
            boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
            boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));

            double xcoordi = 0;
            double ycoordi = 0;
            double boxWidth = 0;
            double boxHeight = 0;

            for (int i = 0; i < arrayBoxes.size(); i++) {
                arrayBoxes.get(i).setWidth(endX, arrayBoxes.get(i).xcoordi);
                arrayBoxes.get(i).setHeight(endY, arrayBoxes.get(i).ycoordi);
                xcoordi = arrayBoxes.get(i).getXcoordi();
                ycoordi = arrayBoxes.get(i).getYcoordi();
                boxWidth = arrayBoxes.get(i).getWidth();
                boxHeight = arrayBoxes.get(i).getHeight();
            }

                mouseIsDown=false;

                context.beginPath();
                context.setStrokeStyle("green");
                context.setLineWidth(2);
                context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
                context.stroke();
                context.fill();

            System.out.println(arrayBoxes.toString());
            mouseMoveListeners.forEach(Runnable::run);

        }).addEventData("event.x").addEventData("event.y");

        element.addEventListener("mousemove", event -> {  // Retrieve Mouse Position when Moving

                JsonObject mousePos = event.getEventData();

                double mouseX = mousePos.getNumber("event.x");
                double mouseY = mousePos.getNumber("event.y");

                MousePosition currentPos = new MousePosition(mouseX, mouseY);
                mousePosArray.add(0, currentPos);
                setMousePosArray(mousePosArray);
            mouseMoveListeners.forEach(Runnable::run);

     }).addEventData("event.x").addEventData("event.y");

    }


    public static void undoLast() {
        if (arrayBoxes.size() > 0) {
            arrayBoxes.remove(arrayBoxes.size() - 1);
        }
//        System.out.println(arrayBoxes.toString());
        System.out.println(arrayBoxes.get(0).toString());
        System.out.println(arrayBoxes.size());

        for (int i = 0; i < arrayBoxes.size(); i++){
            context.clearRect(0, 0, 1580, 700);
            context.beginPath();
            context.setStrokeStyle("limegreen");
            context.setLineWidth(2);
            context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
            context.fill();
        }

    }

    /**
     * Gets the context for rendering shapes and images in the canvas.
     * <p>
     * It is a Java wrapper for the <a href=
     * "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
     * client-side API</a>.
     *
     * @return the 2D rendering context of this canvas
     */
    public CanvasRenderingContext2D getContext() {
        return context;
    }

    /**
     * {@inheritDoc}
     * <p>
     * <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
     * drawing, and it uses the width and height provided in the constructor.
     * This coordinate system is independent of the component's size. Changing
     * the component's size with this method may scale/stretch the rendered
     * graphics.
     */
    @Override
    public void setWidth(String width) {
        HasSize.super.setWidth(width);
    }

    /**
     * {@inheritDoc}
     * <p>
     * <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
     * drawing, and it uses the width and height provided in the constructor.
     * This coordinate system is independent of the component's size. Changing
     * the component's size with this method may scale/stretch the rendered
     * graphics.
     */
    @Override
    public void setHeight(String height) {
        HasSize.super.setHeight(height);
    }

    /**
     * {@inheritDoc}
     * <p>
     * <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
     * drawing, and it uses the width and height provided in the constructor.
     * This coordinate system is independent of the component's size. Changing
     * the component's size with this method may scale/stretch the rendered
     * graphics.
     */
    @Override
    public void setSizeFull() {
        HasSize.super.setSizeFull();
    }

    public void addComponent(Label label) {
    }
}

CanvasRenderingContext2D.java:

package com.vaadin.starter.beveragebuddy.ui.components;

import com.vaadin.starter.beveragebuddy.backend.MainLayout;

import java.io.Serializable;
import java.util.ArrayList;

/**
 * The context for rendering shapes and images on a canvas.
 * <p>
 * This is a Java wrapper for the <a href=
 * "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
 * client-side API</a>.
 */
public class CanvasRenderingContext2D {

    private Canvas canvas;
    public static ArrayList<BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();

    protected CanvasRenderingContext2D(Canvas canvas) {
        this.canvas = canvas;
    }

    public void setFillStyle(String fillStyle) {
        setProperty("fillStyle", fillStyle);
    }

    public void setStrokeStyle(String strokeStyle) {
        setProperty("strokeStyle", strokeStyle);
    }

    public void setLineWidth(double lineWidth) {
        setProperty("lineWidth", lineWidth);
    }

    public void setFont(String font) {
        setProperty("font", font);
    }

    public void arc(double x, double y, double radius, double startAngle,
            double endAngle, boolean antiClockwise) {
        callJsMethod("arc", x, y, radius, startAngle, endAngle, antiClockwise);
    }

    public void beginPath() {
        callJsMethod("beginPath");
    }

    public void clearRect(double x, double y, double width, double height) {
        callJsMethod("clearRect", x, y, width, height);
        Canvas.arrayBoxes.clear();
        System.out.println(arrayBoxes.toString());
        MainLayout.count = 0;
    }

    public void closePath() {
        callJsMethod("closePath");
    }

    /**
     * Fetches the image from the given location and draws it on the canvas.
     * <p>
     * <b>NOTE:</b> The drawing will happen asynchronously after the browser has
     * received the image.
     * 
     * @param src
     *            the url of the image to draw
     * @param x
     *            the x-coordinate of the top-left corner of the image
     * @param y
     *            the y-coordinate of the top-left corner of the image
     */
    public void drawImage(String src, double x, double y) {
        runScript(String.format(
                "var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {"
                        + "$0.getContext('2d').drawImage(zwKqdZ, %s, %s);};"
                        + "zwKqdZ.src='%s';",
                x, y, src));
    }

    /**
     * Fetches the image from the given location and draws it on the canvas.
     * <p>
     * <b>NOTE:</b> The drawing will happen asynchronously after the browser has
     * received the image.
     * 
     * @param src
     *            the url of the image to draw
     * @param x
     *            the x-coordinate of the top-left corner of the image
     * @param y
     *            the y-coordinate of the top-left corner of the image
     * @param width
     *            the width for the image
     * @param height
     *            the height for the image
     */
    public void drawImage(String src, double x, double y, double width,
            double height) {
        runScript(String.format("var zwKqdZ = new Image();"
                + "zwKqdZ.onload = function () {"
                + "$0.getContext('2d').drawImage(zwKqdZ, %s, %s, %s, %s);};"
                + "zwKqdZ.src='%s';", x, y, width, height, src));
    }

    public void fill() {
        callJsMethod("fill");
    }

    public void fillRect(double x, double y, double width, double height) {
        callJsMethod("fillRect", x, y, width, height);
    }

    public void fillText(String text, double x, double y) {
        callJsMethod("fillText", text, x, y);
    }

    public void lineTo(double x, double y) {
        callJsMethod("lineTo", x, y);
    }

    public void moveTo(double x, double y) {
        callJsMethod("moveTo", x, y);
    }

    public void rect(double x, double y, double width, double height) {
        callJsMethod("rect", x, y, width, height);
    }

    public void restore() {
        callJsMethod("restore");
    }

    public void rotate(double angle) {
        callJsMethod("rotate", angle);
    }

    public void save() {
        callJsMethod("save");
    }

    public void scale(double x, double y) {
        callJsMethod("scale", x, y);
    }

    public void stroke() {
        callJsMethod("stroke");
    }

    public void strokeRect(double x, double y, double width, double height) {
        callJsMethod("strokeRect", x, y, width, height);
    }

    public void strokeText(String text, double x, double y) {
        callJsMethod("strokeText", text, x, y);
    }

    public void translate(double x, double y) {
        callJsMethod("translate", x, y);
    }

    protected void setProperty(String propertyName, Serializable value) {
        runScript(String.format("$0.getContext('2d').%s='%s'", propertyName,
                value));
    }

    /**
     * Runs the given js so that the execution order works with callJsMethod().
     * Any $0 in the script will refer to the canvas element.
     */
    private void runScript(String script) {
        canvas.getElement().getNode().runWhenAttached(
                // This structure is needed to make the execution order work
                // with Element.callFunction() which is used in callJsMethod()
                ui -> ui.getInternals().getStateTree().beforeClientResponse(
                        canvas.getElement().getNode(),
                        context -> ui.getPage().executeJavaScript(script,
                                canvas.getElement())));
    }

    protected void callJsMethod(String methodName, Serializable... parameters) {
        canvas.getElement().callFunction("getContext('2d')." + methodName,
                parameters);
    }

}

BoundingBox.java:

package com.vaadin.starter.beveragebuddy.ui.components;

public class BoundingBox {

    public double xcoordi = 0;
    public double ycoordi = 0;
    public double boxWidth = 0;
    public double boxHeight = 0;
    public String boxname;
    public String boxcategory;

    public BoundingBox(String boxname, String boxcategory, double xcoordi, double ycoordi, double boxWidth, double boxHeight) {
        this.boxname = boxname;
        this.boxcategory = boxcategory;
        this.xcoordi = xcoordi;
        this.ycoordi = ycoordi;
        this.boxWidth = boxWidth;
        this.boxHeight = boxHeight;
    }

    public String getBoxName() {
        return boxname;
    }

    public void setName(String boxname) {
        this.boxname = boxname;
    }

    public String getBoxcategory() {
        return boxcategory;
    }

    public void setBoxcategory(String boxcategory) {
        this.boxcategory = boxcategory;
    }

    public double getXcoordi() {
        return xcoordi;
    }

    public void setXcoordi(double xcoordi) {
        this.xcoordi = xcoordi;
    }

    public double getYcoordi() {
        return ycoordi;
    }

    public void setYcoordi(double ycoordi) {
        this.ycoordi = ycoordi;
    }

    public double getWidth() {
        return boxWidth;
    }

    public void setWidth(double endX, double xcoordi) {
        boxWidth = endX - xcoordi;
    }

    public double getHeight() {
        return boxHeight;
    }

    public void setHeight(double endY, double ycoordi) {
        boxHeight = endY - ycoordi;
    }

    @Override
    public String toString() {
        return "{" +
                "Name=" + boxname +
                ", Class=" + boxcategory +
                ", X=" + xcoordi +
                ", Y=" + ycoordi +
                ", Width=" + boxWidth +
                ", Height=" + boxHeight +
                '}';
    }
}

MousePosition.java:

package com.vaadin.starter.beveragebuddy.ui.components;

public class MousePosition {

    public double mouseX;
    public double mouseY;

    public MousePosition(double mouseX, double mouseY) {
        this.mouseX = mouseX;
        this.mouseY = mouseY;
    }

    public double getMouseX() {
        return mouseX;
    }

    public void setMouseX(double mouseX) {
        this.mouseX = mouseX;
    }

    public double getMouseY() {
        return mouseY;
    }

    public void setMouseY(double mouseY) {
        this.mouseY = mouseY;
    }

    @Override
    public String toString() {
        return  "X = " + mouseX +
                ", Y = " + mouseY;
    }
}

堆栈跟踪:

[qtp1975873209-21] ERROR com.vaadin.flow.server.DefaultErrorHandler - 
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.get(ArrayList.java:433)
    at com.vaadin.starter.beveragebuddy.ui.components.Canvas.undoLast(Canvas.java:191)
    at com.vaadin.starter.beveragebuddy.backend.MainLayout.lambda$new$9b1b5227$1(MainLayout.java:67)
    at com.vaadin.flow.component.ComponentEventBus.fireEvent(ComponentEventBus.java:133)
    at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:327)
    at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$5ee67f2b$1(ComponentEventBus.java:191)
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:379)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:379)
    at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:59)
    at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:64)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:377)
    at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$0(ServerRpcHandler.java:367)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:367)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:309)
    at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:89)
    at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
    at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1487)
    at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:300)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
    at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:201)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:499)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:258)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:748)

非常感谢您的帮助,谢谢!

1 个答案:

答案 0 :(得分:2)

在撤消方法中,您具有以下代码

WP_CRON?

您的for (int i = 0; i < arrayBoxes.size(); i++){ context.clearRect(0, 0, 1580, 700); context.beginPath(); context.setStrokeStyle("limegreen"); context.setLineWidth(2); context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight); context.fill(); } 函数包含该行

context.clearRect()

Canvas.arrayBoxes.clear(); 数组中删除所有项目。因此,当您尝试执行arrayBoxes等时,在for循环的末尾会出现异常。