This discussion is archived
4 Replies Latest reply: Dec 5, 2012 11:59 AM by 978168 RSS

Adding nodes to a plane/canvas

978168 Newbie
Currently Being Moderated
I just want to create a simple application with a menu pane at the top and a "drawing" area below it.
The goal is just to add nodes (using a ellippse), moving them around and in the end to have a option to connect the nodes with lines.

This is the first time I am using JavaFX and I did go through the basic tutorials but I am struggling with the layout.

Here I created a Pane() that I add all my nodes to, I then use a VBox to add the menu bar and the Pane underneath it. The following code is what I use to add a node to the pane (center is the pane's name):
 private void addNode(MenuItem add) {
        add.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent t) {

                filter = new EventHandler<MouseEvent>() {
                    @Override
                    public void handle(MouseEvent t) {
                        System.out.println(center.getChildren());

                        crcl = EllipseBuilder.create()
                                .centerX(t.getSceneX())
                                .centerY(t.getSceneY() - 20)
                                .radiusX(20)
                                .radiusY(20)
                                .strokeWidth(0)
                                .fill(Color.color(Math.random(), Math.random(), Math.random()))
                                .build();

                        crcl.setOnMouseEntered(new EventHandler<MouseEvent>() {
                            @Override
                            public void handle(MouseEvent t) {
                                final Delta dragDelta = new Delta();
                                crcl = (Ellipse) t.getSource();

                                //drag events
                                crcl.setOnMousePressed(new EventHandler<MouseEvent>() {
                                    @Override
                                    public void handle(MouseEvent mouseEvent) {
                                        // record a delta distance for the drag and drop operation.
                                        dragDelta.x = crcl.getLayoutX() - mouseEvent.getSceneX();
                                        dragDelta.y = crcl.getLayoutY() - mouseEvent.getSceneY();
                                        crcl.setCursor(Cursor.MOVE);
                                    }
                                });
                                crcl.setOnMouseDragged(new EventHandler<MouseEvent>() {
                                    @Override
                                    public void handle(MouseEvent mouseEvent) {
                                        crcl.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
                                        crcl.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
                                    }
                                });


                            }
                        });
                        center.getChildren().add(crcl);

                        center.removeEventFilter(MouseEvent.MOUSE_PRESSED, filter);
                    }
                };
                center.addEventFilter(MouseEvent.MOUSE_PRESSED, filter);
            }
        });
    }
class Delta {

    double x, y;
}
Adding and dragging the nodes works fine.

I don't know if I am using the Filter correctly, but if I do not use it and I click on the Add option in the menu, I can click multiple times on the center pane and add multiple nodes.

How can I prevent the "add.setOnAction(new EventHandler<ActionEvent>()" to execute multiple times?
And is a pane the best object to use in this case?

thanks.
  • 1. Re: Adding nodes to a plane/canvas
    James_D Guru
    Currently Being Moderated
    Pane is a good choice for the region containing your ellipses.

    You don't really need the handler for mouseEntered on the Circle. You can do:
    // in the filter:
    public void handle(MouseEvent t) {
      crcl = EllipseBuilder.create()
        // ...
      final Delta dragDelta = new Delta();
      crcl.setOnMousePressed(...);
      crcl.setOnMouseDragged(...);
      center.getChildren().add(crcl);
    }
    You have two options (that I can see) for avoiding the slightly complex-looking filter which is added and removed from the mouse handlers for the center.

    One (perhaps the simplest) is just to add the new circle at some default location when the user selects "add" from the menu. This way the user chooses "add", the circle appears (say in the middle of "center"), and then they can drag it around as needed.

    The other is to keep a boolean property indicating if adding is active. Set it in the event handler for the "add" menu item. Just add an event handler to the center which checks the property; if it's set to true, then create the circle and add it, etc, and set the property back to false. No need to remove the handler. So something like:
    private final BooleanProperty addActive = new SimpleBooleanProperty(false);
    ...
    add.setOnAction(new EventHandler<ActionEvent>() {
      public void handle(ActionEvent t) {
        addActive.set(true);
      }
    });
    ...
    center.setOnMousePressed(new EventHandler<MouseEvent>() {
      public void handle(MouseEvent t) {
        if (addActive.get()) {
          final Ellipse crcl = EllipseBuilder.create()... ;
          final Delta dragDelta = new Delta();
          crcl.setOnMousePressed(...);
          crcl.setOnMouseDragged(...);
          center.getChildren().add(crcl);
          addActive.set(false);
        }
      }
    });
    This removes the nested handlers; nothing in particular wrong with that, it's just a little harder to read (and maintain). It would also make it easy to do things like this:
    center.styleProperty().bind(new StringBinding() {
      { super.bind(addActive) ; }
      @Override
      public String computeValue() {
        if (addActive.get()) {
          return "-fx-background-color: Color.YELLOW;" ;
        } else {
          return "-fx-background-color: Color.WHITE;" ;
        }
      }
    });
    which would change the background color of the pane when adding was active.
  • 2. Re: Adding nodes to a plane/canvas
    978168 Newbie
    Currently Being Moderated
    Thanks for the speedy reply James D.

    Your solution is very elegant and is definitely much easier to understand.

    I just have a questions though.
    Could you please explain the advantages of using a BooleanProperty instead of a boolean? And previously I determined which node to move with
    crcl = (Ellipse)t.getSource();
    right after the crcl.setOnMouseEntered MouseEvent.

    How can I do this with your example since when I add another node, I can only move the last node added.

    Thanks
  • 3. Re: Adding nodes to a plane/canvas
    James_D Guru
    Currently Being Moderated
    >
    Could you please explain the advantages of using a BooleanProperty instead of a boolean?
    If you use a BooleanProperty you can make it final; you can't do that with a boolean as you need to be able to change its value (rather than its state).
    The advantage of letting it be final is that you can then define it as a local variable instead of a field in the class, assuming that add.setOnAction(...) and center.setOnMousePressed(...) are invoked in the same method. So I should have removed the keyword "private". It's generally good to scope all variables as narrowly as possible.

    The other advantage of a BooleanProperty is that you can bind to it, as I mentioned at the end of the previous post. This provides an easy way of changing the appearance of the GUI (the background color of the pane, for example) so that the user has a hint that clicking on the pane will add a new node.
    And previously I determined which node to move with
    crcl = (Ellipse)t.getSource();
    right after the crcl.setOnMouseEntered MouseEvent.

    How can I do this with your example since when I add another node, I can only move the last node added.
    You should be able to move any node, whether it was the last one added or not. A mouse listener is registered with each crcl as it's created, so there should be no problem. You can still do
    crcl = (Ellipse)mouseEvent.getSource();
    if you like, but since crcl is defined as a final variable, you could also just reference it directly:
          final Ellipse crcl = EllipseBuilder.create()... ;
          final Delta dragDelta = new Delta();
          crcl.setOnMousePressed(new EventHandler<MouseEvent> {
            public void handle(MouseEvent mouseEvent) {
              dragDelta.x = crcl.getLayoutX() - mouseEvent.getSceneX(); // crcl refers to nearest definition: the final local variable defined above
              ...
            }
          );
    So the user can click on "add", click in the pane, and place a node. Click on "add" again, click in the pane, place a node. Repeat as many times as necessary. Then click on any existing node and drag it around as desired.

    If you don't want this behavior, i.e. if you only want to be able to drag a node around when it's just been created, you can do this:
    final Ellipse crcl = EllipseBuilder.create()... // as before
    ...
    final EventHandler<MouseEvent> mouseDragHandler = new EventHandler<MouseEvent>() {
      public void handle(MouseEvent mouseEvent) {
        dragDelta.x = crcl.getLayoutX() - mouseEvent.getSceneX();
        // etc
      }
    };
    crcl.addEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragHandler);
    crcl.setOnMouseReleased(new EventHandler<MouseEvent>() {
      public void handle(MouseEvent mouseEvent) {
        crcl.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragHandler);
      }
    });
    but I think the ability to move any node probably makes for an easier user interface. This of course depends on your requirements.
  • 4. Re: Adding nodes to a plane/canvas
    978168 Newbie
    Currently Being Moderated
    Thanks James D for all your help!

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points