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
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);
}
}