简体   繁体   中英

Stack<Canvas> in a JavaFX FXML document

I'm creating an image manipulation program in JavaFX using FXML to organize the UI. It currently supports things like loading images, drawing, and saving those changes.

I do not know how/cannot find a way to represent a Stack of Canvas' in the FXML document. My goal is to have a stack of Canvas' that will allow me to undo the changes the user has made by simply clearing the top layer in the stack of canvas'. The idea is that each edit would reside on it's own canvas.

Below is some code from the FXML document. Here is the center pane of my border pane. This is the center of the image manipulating program. Inside of it I have a stack pane so that I can overlay things. The two comments are what I would expect to be able to do but those attempts do not work.

<!-- center pane -->
<center>
    <StackPane>
        <Canvas fx:id="currCanvas" />
        <!-- <Canvas fx:id="canvasStack" /> -->
        <!-- <Stack fx:id="canvasStack" /> -->
        <Canvas fx:id="previewCanvas" />
   </StackPane>
</center>

If I was going to implement this without the FXML document it would be much simpler but it would be more difficult to organize the UI. My confusion is that I do not know how to accomplish this using FXML.

Thanks

All you really need is one Canvas but whenever an edit is made, you should make a snapshot of the canvas or make a copy of the pixels in the canvas then push that array of pixels or object containing the pixel to a Stack. That would be a bad idea because making a copy of the canvas will be very expensive both computationally and memory wise. While feasible, I would advise against it.

To me the best and easiest way to deal with "undo operations" is to draw first on a BufferedImage then draw that image on the Canvas as explained here and here and second to introduce the concept of Action in your application. For instance a "paint action" would look something like:

interface Action 
{
    //Used to re-apply action after it was undone
    public void apply();

    //Undo the action
    public void undo();
}

class PaintAction() implements Action
{
    static class Pixel
    {
        final int x;
        final int y;
        final int oldColor;
        final int newColor;
        PixelPos(int x, int y, int oldColor, int newColor)
        {
            this.x = x;
            this.y = y;
            this.oldColor = oldColor;
            this.newColor = newColor;
        }
    }

    List<Pixel> affectedPixels;

    PaintAction()
    {
        affectedPixels = new ArrayList<>();
    }

    @Override
    public void apply(Canvas canvas)
    {
        for (Pixel pixel : pixel)
        {
            //draw new pixel's color on the canvas 
        }
    }

    @Override
    public void undo(Canvas canvas)
    {
        for (Pixel pixel : pixel)
        {
            //draw old pixel's color on the canvas 
        }
    }

    public void addPixel(Pixel pixel)
    {
        affectedPixels.add(Pixel);
    }
}

So when the user presses the mouse button to start painting, you create a new PaintAction then whenever the user moves the mouse, you would create a new Pixel object and add it to the list of "affected pixels by the PaintAction " then proceed to change the color of the pixel in the BufferedImage .

Then all you would need will be to keep a stack of Action and apply, undo them as you see fit.

Hope that make sense, cheers.

Simply use a Pane . This way there are no issues with aligning the children. Pane s don't provide a Stack of children, but a List can be used for stack operation too, although the removing the last item from the list is a bit more difficult than for a stack, but simple enough.

The following code simply adds circles and rectangles, but you could replace this with adding Canvas es instead:

@Override
public void start(Stage primaryStage) {
    Button doBtn = new Button("draw");
    Button undo = new Button("undo");
    undo.setDisable(true);

    Pane stack = new Pane();
    stack.setPrefSize(400, 400);

    VBox root = new VBox(new HBox(doBtn, undo), stack);

    doBtn.setOnAction(new EventHandler<ActionEvent>() {

        private int count = 0;

        @Override
        public void handle(ActionEvent event) {
            // create & add some node
            Node addedNode;
            switch (count % 2) {
                case 0:
                    addedNode = new Circle(count * 5 + 5, count * 5 + 5, 5, Color.BLUE);
                    break;
                case 1:
                    Rectangle rect = new Rectangle(count * 5, 390 - count * 5, 10, 10);
                    rect.setFill(Color.RED);
                    addedNode = rect;
                    break;
                default:
                    return;
            }
            stack.getChildren().add(addedNode);
            undo.setDisable(false);
            count++;
        }

    });

    undo.setOnAction(evt -> {
        // remove last child
        List<Node> children = stack.getChildren();
        children.remove(children.size() - 1);

        // check, if undo button needs to be disabled
        undo.setDisable(children.isEmpty());
    });

    Scene scene = new Scene(root);
    primaryStage.setScene(scene);
    primaryStage.show();
}

Note that I wouldn't recommend creating new Canvas es for every operation though, since this could lead to memory issues pretty fast...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM