Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to separate elements into classes in JavaFX?

I developed a relatively simple application, using JavaFX, capable of adding a circle shape to the interface every time the user presses a button, in addition to allowing the user to manipulate this object.

I need to separate the program into classes, so I thought about creating a class for the circles, but I don't know how to do that. Can you help me?

This is my AddCircleApp.java:

package addcircleapp;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class AddCircleApp extends Application {
    
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
        
        Scene scene = new Scene(root);
        
        stage.setScene(scene);
        stage.setTitle("Interacting with Objects");
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
    
}

This is my FXMLDocumentController.java:

package addcircleapp;

import java.net.URL;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Slider;
import javafx.scene.input.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.ResourceBundle;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.util.Pair;

public class FXMLDocumentController implements Initializable {

    @FXML
    private Pane containerPane;

    @FXML
    private Button addCircleButton;

    @FXML
    private Button editButton;

    @FXML
    private Button deleteButton;

    @FXML
    private ColorPicker colorPicker;

    @FXML
    private Slider sizeSlider;

    @FXML
    private Label colorSelectLabel;

    @FXML
    private Label sizeSelectLabel;

    private List<Circle> circles = new ArrayList<>();
    private Circle selectedCircle = null;
    private double xOffset = 0;
    private double yOffset = 0;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        sizeSelectLabel.setText("Size: " + String.valueOf(sizeSlider.getValue()));

        sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            sizeSelectLabel.setText("Size: " + String.valueOf(newValue.intValue()));
        });
    }

    public void handleAddCircleButton() {

        Random rand = new Random();
        int x = rand.nextInt(500);
        int y = rand.nextInt(200);

        Color selectedColor = colorPicker.getValue();
        double selectedSize = sizeSlider.getValue();

        Circle circle = new Circle(x, y, selectedSize, selectedColor);

        circle.setOnMouseClicked((MouseEvent e) -> {
            handleCircleClick(circle);
        });

        circle.setOnKeyPressed((KeyEvent e) -> {
            handleCircleMovement(e, circle);
        });

        circle.setOnMousePressed((MouseEvent e) -> {
            handleCircleDragStart(e, circle);
        });

        circle.setOnMouseDragged((MouseEvent e) -> {
            handleCircleDrag(e, circle);
        });

        circle.setOnMouseReleased((MouseEvent e) -> {
            handleCircleDragEnd(e, circle);
        });

        circles.add(circle);
        containerPane.getChildren().add(circle);

        System.out.println("New circle was created!");
    }

    public void handleEditButton() {
        if (selectedCircle != null) {
            Dialog<Pair<Color, Double>> editDialog = new Dialog<>();
            editDialog.setTitle("Edit Circle");
            editDialog.setHeaderText("Edit the color and size of the selected circle.");

            ButtonType confirmButton = new ButtonType("OK", ButtonData.OK_DONE);
            editDialog.getDialogPane().getButtonTypes().addAll(confirmButton, ButtonType.CANCEL);

            ColorPicker colorPicker = new ColorPicker((Color) selectedCircle.getFill());
            Slider sizeSlider = new Slider(10, 100, selectedCircle.getRadius());

            VBox vbox = new VBox(10);
            Label colorLabel = new Label("Color:");
            Label sizeLabel = new Label("Size:");

            sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));

            sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
                sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
            });

            vbox.getChildren().addAll(colorLabel, colorPicker, sizeLabel, sizeSlider);

            editDialog.getDialogPane().setContent(vbox);

            editDialog.setResultConverter((ButtonType dialogButton) -> {
                if (dialogButton == confirmButton) {
                    return new Pair<>(colorPicker.getValue(), sizeSlider.getValue());
                }
                return null;
            });

            Optional<Pair<Color, Double>> result = editDialog.showAndWait();

            result.ifPresent(newValues -> {
                selectedCircle.setFill(newValues.getKey());
                selectedCircle.setRadius(newValues.getValue());
            });
        }
    }

    public void handleDeleteButton(ActionEvent event) {
        if (selectedCircle != null) {
            circles.remove(selectedCircle);
            containerPane.getChildren().remove(selectedCircle);
            selectedCircle = null;
        }
    }

    private void handleCircleClick(Circle clickedCircle) {
        if (selectedCircle != null) {
            selectedCircle.setStroke(Color.TRANSPARENT);
        }

        selectedCircle = clickedCircle;
        Color fillColor = (Color) selectedCircle.getFill();
        Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());

        selectedCircle.setStroke(complementary);
        selectedCircle.setStrokeWidth(2.0);

        selectedCircle.requestFocus();
    }

    public void handleContainerPaneClick(MouseEvent event) {
        if (selectedCircle != null && !selectedCircle.contains(event.getX(), event.getY())) {
            selectedCircle.setStroke(Color.TRANSPARENT);
            selectedCircle = null;
        }
    }

    private void handleCircleMovement(KeyEvent event, Circle circle) {
        if (selectedCircle != null && null != event.getCode()) {
            switch (event.getCode()) {
                case LEFT:
                    circle.setCenterX(circle.getCenterX() - 10);
                    event.consume();
                    break;
                case RIGHT:
                    circle.setCenterX(circle.getCenterX() + 10);
                    event.consume();
                    break;
                case UP:
                    circle.setCenterY(circle.getCenterY() - 10);
                    event.consume();
                    break;
                case DOWN:
                    circle.setCenterY(circle.getCenterY() + 10);
                    event.consume();
                    break;
                default:
                    break;
            }
        }

    }

    public void handleCircleDragStart(MouseEvent event, Circle circle) {
        xOffset = event.getSceneX() - circle.getCenterX();
        yOffset = event.getSceneY() - circle.getCenterY();
    }

    public void handleCircleDrag(MouseEvent event, Circle circle) {
        double x = event.getSceneX() - xOffset;
        double y = event.getSceneY() - yOffset;

        circle.setCenterX(x);
        circle.setCenterY(y);
    }

    public void handleCircleDragEnd(MouseEvent event, Circle circle) {
        xOffset = 0;
        yOffset = 0;
    }

}

This is my FXMLDocument.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.Pane?>

<Pane fx:id="containerPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onMouseClicked="#handleContainerPaneClick" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="addcircleapp.FXMLDocumentController">
   <children>
      <Button fx:id="addCircleButton" layoutX="345.0" layoutY="362.0" mnemonicParsing="false" onAction="#handleAddCircleButton" text="AddCircle" />
      <ColorPicker fx:id="colorPicker" layoutX="14.0" layoutY="361.0" />
      <Slider fx:id="sizeSlider" blockIncrement="1.0" layoutX="180.0" layoutY="367.0" min="1.0" value="50.0" />
      <Button fx:id="editButton" layoutX="432.0" layoutY="362.0" mnemonicParsing="false" onAction="#handleEditButton" prefHeight="25.0" prefWidth="67.0" text="Edit" />
      <Button fx:id="deleteButton" layoutX="519.0" layoutY="361.0" mnemonicParsing="false" onAction="#handleDeleteButton" prefHeight="25.0" prefWidth="67.0" text="Delete" />
      <Label fx:id="colorSelectLabel" layoutX="14.0" layoutY="340.0" text="Select a color:" />
      <Label fx:id="sizeSelectLabel" layoutX="180.0" layoutY="340.0" text="Size:" />
   </children>
</Pane>

I managed to create a "MyCircle" class and add some event handlers, but some features are not working correctly, such as the handleCircleClick method. Also, I don't know how to resolve the handleEditButton and handleDeleteButton methods.

My FXMLDocumentController.java file looked like this:

package addcircleapp;

import java.net.URL;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Slider;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.ResourceBundle;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.util.Pair;

public class FXMLDocumentController implements Initializable {

    @FXML
    private Pane containerPane;

    @FXML
    private Button addCircleButton;

    @FXML
    private Button editButton;

    @FXML
    private Button deleteButton;

    @FXML
    private ColorPicker colorPicker;

    @FXML
    private Slider sizeSlider;

    @FXML
    private Label colorSelectLabel;

    @FXML
    private Label sizeSelectLabel;

    private List<MyCircle> circles = new ArrayList<>();
    private MyCircle selectedCircle = null;
    //private double xOffset = 0;
    //private double yOffset = 0;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        sizeSelectLabel.setText("Size: " + String.valueOf(sizeSlider.getValue()));

        sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            sizeSelectLabel.setText("Size: " + String.valueOf(newValue.intValue()));
        });
    }

    public void handleAddCircleButton() {

        Random rand = new Random();
        int x = rand.nextInt(500);
        int y = rand.nextInt(200);

        Color selectedColor = colorPicker.getValue();
        double selectedSize = sizeSlider.getValue();

        MyCircle circle = new MyCircle(x, y, selectedSize, selectedColor);

        /*circle.setOnMouseClicked((MouseEvent e) -> {
        handleCircleClick(circle);
        });
        
        circle.setOnKeyPressed((KeyEvent e) -> {
        handleCircleMovement(e, circle);
        });
        
        circle.setOnMousePressed((MouseEvent e) -> {
        handleCircleDragStart(e, circle);
        });
        
        circle.setOnMouseDragged((MouseEvent e) -> {
        handleCircleDrag(e, circle);
        });
        
        circle.setOnMouseReleased((MouseEvent e) -> {
        handleCircleDragEnd(e, circle);
        });*/
        circles.add(circle);
        containerPane.getChildren().add(circle);

        System.out.println("New circle was created!");
    }

    public void handleEditButton() {
        if (selectedCircle != null) {
            Dialog<Pair<Color, Double>> editDialog = new Dialog<>();
            editDialog.setTitle("Edit Circle");
            editDialog.setHeaderText("Edit the color and size of the selected circle.");

            ButtonType confirmButton = new ButtonType("OK", ButtonData.OK_DONE);
            editDialog.getDialogPane().getButtonTypes().addAll(confirmButton, ButtonType.CANCEL);

            ColorPicker colorPicker = new ColorPicker((Color) selectedCircle.getFill());
            Slider sizeSlider = new Slider(10, 100, selectedCircle.getRadius());

            VBox vbox = new VBox(10);
            Label colorLabel = new Label("Color:");
            Label sizeLabel = new Label("Size:");

            sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));

            sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
                sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
            });

            vbox.getChildren().addAll(colorLabel, colorPicker, sizeLabel, sizeSlider);

            editDialog.getDialogPane().setContent(vbox);

            editDialog.setResultConverter((ButtonType dialogButton) -> {
                if (dialogButton == confirmButton) {
                    return new Pair<>(colorPicker.getValue(), sizeSlider.getValue());
                }
                return null;
            });

            Optional<Pair<Color, Double>> result = editDialog.showAndWait();

            result.ifPresent(newValues -> {
                selectedCircle.setFill(newValues.getKey());
                selectedCircle.setRadius(newValues.getValue());
            });
        }
    }

    public void handleDeleteButton(ActionEvent event) {
        if (selectedCircle != null) {
            circles.remove(selectedCircle);
            containerPane.getChildren().remove(selectedCircle);
            selectedCircle = null;
        }
    }

    /*private void handleCircleClick(MyCircle clickedCircle) {
    if (selectedCircle != null) {
    selectedCircle.setStroke(Color.TRANSPARENT);
    }
    
    selectedCircle = clickedCircle;
    Color fillColor = (Color) selectedCircle.getFill();
    Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
    
    selectedCircle.setStroke(complementary);
    selectedCircle.setStrokeWidth(2.0);
    
    selectedCircle.requestFocus();
    }*/

    public void handleContainerPaneClick(MouseEvent event) {
        if (selectedCircle != null && !selectedCircle.contains(event.getX(), event.getY())) {
            selectedCircle.setStroke(Color.TRANSPARENT);
            selectedCircle = null;
        }
    }

    /*
    private void handleCircleMovement(KeyEvent event, MyCircle circle) {
    if (selectedCircle != null && null != event.getCode()) {
    switch (event.getCode()) {
    case LEFT:
    circle.setCenterX(circle.getCenterX() - 10);
    event.consume();
    break;
    case RIGHT:
    circle.setCenterX(circle.getCenterX() + 10);
    event.consume();
    break;
    case UP:
    circle.setCenterY(circle.getCenterY() - 10);
    event.consume();
    break;
    case DOWN:
    circle.setCenterY(circle.getCenterY() + 10);
    event.consume();
    break;
    default:
    break;
    }
    }
    
    }
    
    public void handleCircleDragStart(MouseEvent event, MyCircle circle) {
    xOffset = event.getSceneX() - circle.getCenterX();
    yOffset = event.getSceneY() - circle.getCenterY();
    }
    
    public void handleCircleDrag(MouseEvent event, MyCircle circle) {
    double x = event.getSceneX() - xOffset;
    double y = event.getSceneY() - yOffset;
    
    circle.setCenterX(x);
    circle.setCenterY(y);
    }
    
    public void handleCircleDragEnd(MouseEvent event, MyCircle circle) {
    xOffset = 0;
    yOffset = 0;
    }*/

}

My MyCircle.java file looked like this:

package addcircleapp;

import javafx.scene.input.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

public class MyCircle extends Circle {
    double xOffset = 0;
    double yOffset = 0;
    MyCircle selectedCircle = null;
    
    public MyCircle(int xPos, int yPos, double radius, Color color){
        super(xPos, yPos, radius, color);
        
        setOnMouseClicked((MouseEvent event) ->{
            //Handle circle click
            MyCircle selectedCircle = (MyCircle) event.getTarget();
            
            if(selectedCircle.isSelected()){
                selectedCircle.deselect();
            } else {
                selectedCircle.select();
            }
        });
        
        setOnKeyPressed((KeyEvent event) ->{
            //Handle circle movement
            handleCircleMovement(event, this);
        });
        
        setOnMousePressed((MouseEvent event) ->{
            //Handle drag start
            handleCircleDragStart(event, this);
        });
        
        setOnMouseDragged((MouseEvent event) ->{
            //Handle drag
            handleCircleDrag(event, this);
        });
        
        setOnMouseReleased((MouseEvent event) ->{
            //Handle drag end
            handleCircleDragEnd(this);
        });
    }
    
    public void select(){
        if(selectedCircle != null){
            selectedCircle.setStroke(Color.TRANSPARENT);
        }
        
        Color fillColor = (Color) getFill();
        Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
        
        setStroke(complementary);
        setStrokeWidth(2.0);
        
        requestFocus();
    }
    
    public void deselect(){
        setStroke(Color.TRANSPARENT);
    }
    
    public boolean isSelected(){
        return getStroke() != Color.TRANSPARENT;
    }
    
    public void handleCircleMovement(KeyEvent event, MyCircle circle){
        if(null != event.getCode())switch (event.getCode()) {
            case LEFT:
                circle.setCenterX(circle.getCenterX() - 10);
                event.consume();
                break;
            case RIGHT:
                circle.setCenterX(circle.getCenterX() + 10);
                event.consume();
                break;
            case UP:
                circle.setCenterY(circle.getCenterY() - 10);
                event.consume();
                break;
            case DOWN:
                circle.setCenterY(circle.getCenterY() + 10);
                event.consume();
                break;
            default:
                break;
        }
    }
    
    public void handleCircleDragStart(MouseEvent event, MyCircle circle){
        xOffset = event.getSceneX() - circle.getCenterX();
        yOffset = event.getSceneY() - circle.getCenterY();
    }
    
    public void handleCircleDrag(MouseEvent event, MyCircle circle){
        double x = event.getSceneX() - xOffset;
        double y = event.getSceneY() - yOffset;
        
        circle.setCenterX(x);
        circle.setCenterY(y);
    }
    
    public void handleCircleDragEnd(MyCircle circle){
        xOffset = 0;
        yOffset = 0;
    }
}

like image 510
Enzo Pirineli Pacheco Avatar asked Oct 25 '25 14:10

Enzo Pirineli Pacheco


1 Answers

This is a pretty broad question and is arguably a bit too opinion-based for this site, but I will try to give a couple of pointers.

  1. Your classes should each have a single responsibility, and should only contain fields and methods related to that responsibility. If you have a class called MyCircle, each instance of that class represents a single circle. "Which circle is selected" is not part of the responsibility of that class (it is part of the broader application). In fact, you are implementing a business rule (that perhaps you haven't actually verbalized), that no more than one circle can be selected at once. It certainly is not the responsibility of MyCircle (which again, just represents a single circle) to implement that rule (or to keep track of which of the many circles is selected).

    On the other hand, "is this circle selected" probably is a responsibility of the MyCircle class. So some kind of boolean selected property might well be appropriate here. It's not too hard to see how to change such a property in response to a mouse click.

    This is actually the reason you're having trouble implementing this; you're trying to maintain the state of "which circle is selected" in every circle instance.

  2. Favor composition over inheritance. This is one of the key items in Joshua Bloch's book Effective Java (which every Java Programmer should read). My particular rule of thumb is that if you are not adding functionality to a class, but are just providing a convenient way to configure instances of it, you should not subclass it. Even if you are adding some functionality (and in your case you can argue you are: you are adding the notion of selection), you might favor composition.

    Inheriting from a class means you have access to more of the inner workings of that class, and consequently you have more responsibility in terms of understanding how the class works in order not to write code which might not work the way you want. ("Inheritance violates encapsulation", to quote Bloch.) The more complex a class is (and UI classes are very complex), the more wary you should be of subclassing them. I would not recommend subclassing Circle here.

The first point creates a potential problem: we do need somewhere to track which circle is selected. If each circle only knows when it is selected, we need to know when any of the selected values (one for each circle) changes. JavaFX provides a mechanism for doing this via JavaFX Properties. These are basically "observable" properties: meaning we can register a listener with them and the listener is invoked if the property value changes.

So I would do:

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

public class SelectableCircle {
    double xOffset = 0;
    double yOffset = 0;

    private final Circle uiComponent;

    private final BooleanProperty selected = new SimpleBooleanProperty(false);

    public BooleanProperty selectedProperty() {
        return selected;
    }
    public final boolean isSelected() {
        return selectedProperty().get();
    }
    public final void setSelected(boolean selected) {
        selectedProperty().set(selected);
    }

    public SelectableCircle(int xPos, int yPos, double radius, Color color){
        uiComponent = new Circle(xPos, yPos, radius, color);

        uiComponent.setOnMouseClicked(e -> {
            setSelected( ! isSelected() );
        });

        uiComponent.setOnKeyPressed((KeyEvent event) ->{
            //Handle circle movement
            handleCircleMovement(event);
        });
        uiComponent.setOnMousePressed((MouseEvent event) ->{
            //Handle drag start
            handleCircleDragStart(event);
        });

        uiComponent.setOnMouseDragged((MouseEvent event) ->{
            //Handle drag
            handleCircleDrag(event);
        });


        // update circle ui according to selected state:
        selected.addListener((obs, wasSelected, isNowSelected) -> {
            if (isNowSelected) {
                Color fillColor = (Color) uiComponent.getFill();
                Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
                uiComponent.setStroke(complementary);
                uiComponent.requestFocus();
            } else {
                uiComponent.setStroke(Color.TRANSPARENT);
            }
        });
    }



    public void handleCircleMovement(KeyEvent event){
        switch (event.getCode()) {
            case LEFT:
                uiComponent.setCenterX(uiComponent.getCenterX() - 10);
                event.consume();
                break;
            case RIGHT:
                uiComponent.setCenterX(uiComponent.getCenterX() + 10);
                event.consume();
                break;
            case UP:
                uiComponent.setCenterY(uiComponent.getCenterY() - 10);
                event.consume();
                break;
            case DOWN:
                uiComponent.setCenterY(uiComponent.getCenterY() + 10);
                event.consume();
                break;
            default:
                break;
        }
    }

    public void handleCircleDragStart(MouseEvent event){
        xOffset = event.getSceneX() - uiComponent.getCenterX();
        yOffset = event.getSceneY() - uiComponent.getCenterY();
    }

    public void handleCircleDrag(MouseEvent event){
        double x = event.getSceneX() - xOffset;
        double y = event.getSceneY() - yOffset;

        uiComponent.setCenterX(x);
        uiComponent.setCenterY(y);
    }

    public Circle getUIComponent() {
        return uiComponent;
    }

}

Now you will need a few changes in your controller code. When you add the circle, you need to do something like this:

    public void handleAddCircleButton() {

        Random rand = new Random();
        int x = rand.nextInt(500);
        int y = rand.nextInt(200);

        Color selectedColor = colorPicker.getValue();
        double selectedSize = sizeSlider.getValue();

        SelectableCircle circle = new SelectableCircle(x, y, selectedSize, selectedColor);

        // if selected state of circle changes, update our selectedCircle:
        circle.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
            if (selectedCircle != null) {
                selectedCircle.setSelected(false);
                selectedCircle = null;
            }
            if (isNowSelected) {
                selectedCircle = circle;
            }
        });

        circles.add(circle);
        containerPane.getChildren().add(circle.getUIComponent());

        System.out.println("New circle was created!");
    }

To clear the selection, all we need to do is tell the current selection not to be selected any more (the listener takes care of the rest):

    public void handleContainerPaneClick(MouseEvent event) {
        if (selectedCircle != null && !selectedCircle.getUIComponent().contains(event.getX(), event.getY())) {
            selectedCircle.setSelected(false);
        }
    }

and then in various places (basically when working with the UI) you will need to refer to selectedCircle.getUIComponent() instead of just selectedCircle. For example:

    public void handleDeleteButton(ActionEvent event) {
        if (selectedCircle != null) {
            circles.remove(selectedCircle);
            containerPane.getChildren().remove(selectedCircle.getUIComponent());
            selectedCircle = null;
        }
    }
like image 196
James_D Avatar answered Oct 27 '25 03:10

James_D