Create and integrate custom events in a Java Swing App

46 Views Asked by At

I've been creating a Java Swing app which tracks the position of dummy cars and plots them on a map. I'm afraid that the app is getting bigger and I need a sort of split between UI and app logic.

Is it possible to implement an event/listener architecture with different types of events to update the UI when necessary? How?

At present, I manage to update car position in this way:

class car {
    setPosition(int x, int y) {
        _x = x; _y = y;
        carImage.setPosition(x,y);
    }
    changeImage(enum typeOfImage) {
        carImage.changeImage(typeOfImage);
    }
}

As you can see I need a UI object inside my logic app, and this breaks the split between UI and logic. In addition, I would like to change the logo of the car whenever the car's state changes and hide or show the image at my convenience. So I think that an observer pattern should be suitable in this scenario.

Thank you

1

There are 1 best solutions below

0
Hovercraft Full Of Eels On BEST ANSWER

One way to add "events" to any code is to use a listener framework, and a clean one to use is the java.beans Property Change Listener framework. You would add to the model classes a support object and methods that allow addition or removal of property change listener objects. This will allow outside code to be notified of what changes occur and when. For example:

import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;

public class Car {
    public static final String POSITION = "position";
    public static final String IMAGE = "image";
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
    private CarPosition carPosition;
    private TypeOfImage carImage;

    public void setPosition(int x, int y) {
        CarPosition oldPosition = carPosition;
        this.carPosition = new CarPosition(x, y);
        pcSupport.firePropertyChange(POSITION, oldPosition, carPosition);
    }

    public void setImage(TypeOfImage typeOfImage) {
        TypeOfImage oldImage = typeOfImage;
        this.carImage = typeOfImage;
        pcSupport.firePropertyChange(IMAGE, oldImage, typeOfImage);
    }

    public int getX() {
        return carPosition.x();
    }

    public int getY() {
        return carPosition.y();
    }

    public CarPosition getCarPosition() {
        return carPosition;
    }

    public TypeOfImage getImage() {
        return carImage;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.removePropertyChangeListener(listener);
    }
}

Assuming a simple enum

enum TypeOfImage {
    RED_CAR, BLUE_CAR, GREEN_CAR
}

Best to have the listener return a single object, and so if changing position in one method, have a record to allow this to be returned

record CarPosition(int x, int y) {
}

Then this can be used in the view as follows

class CarView extends JPanel {
    private Car car;

    public CarView(Car car) {
        this.car = car;
        car.addPropertyChangeListener(e -> {
            if (e.getPropertyName().equals(Car.POSITION)) {
                repaint();
            }
        });
        car.addPropertyChangeListener(e -> {
            if (e.getPropertyName().equals(Car.IMAGE)) {
                repaint();
            }
        });
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(car.getImage().getSprite(), car.getX(), car.getY(), null);
    }
}