Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Swing - How to create the best communication between the model and the view

The biggest issue I deal with when I develop desktop applications using Swing is the spaghetti that I result in when I complete the application.

I come from a PHP background with MVC where I had an easy way to implement dependency injection and I just had access to anywhere in the model layer when I need it, I just constructed the required classes.

But in Java it is a bit more complicated for me, because Swing components are basically little models that do things, the thing is, how do I deal with it to come with a nice and non-spaghetti design.

What I call spaghetti design?

For me, spaghetti design is where I sometimes get to a point that in my component, I need access to a another component, or I need access to somewhere that I don't have it constructed. For this, I need to pass these instances from component to component and that means I create access for for these object(s) for components that will never use it and are just being used as a man in the middle to pass the instance. Basically a point where I ask myself.

Let's look at this dummy example:

You have the component Map. This component should draw you a map of your country, and draw entities (An entity can be just some city label, a military base, an radar target data and anything that a map can display really). So in your model layer you probably have somewhere defined a class named EntitiyManager which contains a list of entities, each entity is something that inherits MapEntity. So what do you have to do in order to draw these entities?

Approach 1

Pass EntityManager to Map and retrieve data from EntityManager to draw:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;
    
    public Map(EntityManager manager) {
        this.manager = manager;
    }
    
    // SOME CODE HERE ALOT OF CODE
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        
        Graphics2D g2d = (Graphics2D) g;
        
        for (Entity e : manager.getEntities()) {
            if (!e.isVisible()) {
                continue;
            }
            
            if (e instanceof CityLabel) {
                // TO DRAW FOR CITY LABEL
            } else if (e instanceof MilitaryBase) {
                // TO DRAW FOR MILITARY BASE
            }
        }
    }
}

Approach 2

Pass EntityManager to Map and each Entity will have its own drawing method:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;

    public Map(EntityManager manager) {
        this.manager = manager;
    }

    // SOME CODE HERE ALOT OF CODE

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        manager.render(g2d);
    }
}

Problem with approach 1: It's long and messy and is not organized at all, makes your paint method be too big and hard to go through. Even though you can split it into methods.

Problems with approach 2: EntityManager is not a view, it should not be drawing anything, it should only be used for operations such as algorithms, data management and such.

But in general, I don't like the fact that I passed my EntityManager like this to the view, it just doesn't look good for me.

Now let's say I have to create a function that makes me able to measure distance on the map. I need a mouseEventListener and that mouse event listener needs access to somewhere that it can pass the input to. I need a place to manage that distance measure at. What if I want to create a little window component that shows how many km I measured and on the other side I want to show on the map the dashed line of my measurement, that means I need to use measure distance in two components, these two are related to each other.

I plan to read about Spring this weekend and see how it can help me.

Another problem that comes to me every time is, I don't really know how I can make my controller pass input to the model correctly, I can't really find a good way to design a controller in Swing because how the components are designed. So for me Input management is also confusing and just makes me do a mess in my program.

So what are good approaches to design a perfect Swing application?

Update

My biggest problem comes when I have a view and I have to update another view when something is changed in the current view.

Let's say we have a Map view. The map view is the map of your country. When you move your mouse, you get an event called for that view mouseMoved or something like that, then you get the X, and Y of the mouse in that Map. Now, what we have to do is

  1. Convert X, Y to map latitude and longitude which is done in the model for that view,
  2. We have another view which is named BottomInformation which is basically a bar at the bottom of the frame, and it shows information. We need to update the BottomInformation view to show these longitude and latitude we got.

This means Map needs access to BottomInformation. How do I do it properly without spaghetti? Are there different approaches than trying MVP and MVC?

like image 758
Artemkller545 Avatar asked Aug 16 '16 17:08

Artemkller545


People also ask

Is Java Swing still used in 2022?

JavaFX new fixes will continue to be supported on Java SE 8 through March 2022 and removed from Java SE 11. Swing and AWT will continue to be supported on Java SE 8 through at least March 2025, and on Java SE 11 (18.9 LTS) through at least September 2026.

What are MVC explain how MVC is used in the Java Swing?

Swing uses the model-view-controller architecture (MVC) as the fundamental design behind each of its components. Essentially, MVC breaks GUI components into three elements. Each of these elements plays a crucial role in how the component behaves. The model encompasses the state data for each component.

What are the 3 types of Java Swing containers?

As we mentioned before, Swing provides three generally useful top-level container classes: JFrame , JDialog , and JApplet .

Which design pattern does Swing implement to create a hierarchy of user interface?

The Swing toolkit uses a modified MVC design pattern. It has a single UI object for both the view and the controller. This modified MVC is sometimes called a separable model architecture. In the Swing toolkit, every component has its model, even the basic ones like buttons.


1 Answers

I also encounter those "spaghetti" when I start a complex MVC Swing application. Here is how I manage it. But this is my own way to do, maybe you'll not agree with me ! If anyone disagree with any of my words, just let me know. I'm not a professional, just trying to help. :)


Reminder

First, 'Boris The Spider' already introduced you good MVC/MVP principles. I'll just remember you the basis :

MVC Schema

This means that your controller need a direct access to model, and view objects. The view has access to the model object. The model works alone. It means that if you change your model (changing the API used, changing algorithms,...), the view doesn't care, it just show it up by a few methods.

The controller has to listen for user actions (buttons, fields, etc.) and to change the model data according to this.

The view is only able to show your data from the model. Nothing else.


How to implement

Instancing the objects

First, I create the model and the view objects.

public static void main(String[] args) {
    EntityManager entManager = new EntityManager();
    Map mapView = new Map(entManager);
}

You create your objects, passing through constructor parameters the references needed by each of them. The model is always the first to be created because it is independent.

Then, you create the view, which needs the model to get the data values.

I create the controller inside the view, after creating all the GUI objects. This way, I attach them to their listener in a method attachListeners().

Drawing entities

My approach is that the view needs a method for each Entity type. For example a drawCity() method, then drawRoad() ordrawOilStation(). Because the view is the only object which has to know how to draw them. The Model doesn't have to deal with it.

Because your example need to paint all of the components, the paintComponents() method will be quite a mess, yes. In my opinion, your "approach 1" is the best way to do that.

@Override
public void paintComponent(Graphics g) {
   super.paintComponent(g);

  Graphics2D g2d = (Graphics2D) g;

   for (Entity e : manager.getEntities()) {
       if (!e.isVisible()) {
           continue;
       }

       if (e instanceof CityLabel) {
           drawCity();
       } else if (e instanceof MilitaryBase) {
           drawMilitaryBase();
       }
   }
}

Let's imagine you have a GUI with some JList and JPanel. Your load methods (to read the data to show from the model) will be called separately, each one getting the data they need from the model.

For example :

public void createInterface(){
    //Instanciate some Swing components

    fillPhoneList();
    updateNamePanel();
}

private void fillPhoneList(){
    List<PhoneNumber> phoneList = model.getPhoneList();
    for(PhoneNumber phone : phoneList){
         phoneListView.add(phone);
    }
}

Updating the view

1) Changing views from user actions

Often, the controller reacts when an interface event appeared. The controller's work is to tell the model to change, and, in the same time, to update the view calling the "load" methods.

Let's say you have 2 views, Map and EntityDetails. You have a controller for each of them, called MapListener and EntityDetailsController. They both have, as attribute, the corresponding view object. They catch all user action from there views.

public class MapListener implements ActionListener{
    private Map mapView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}

Something I'm doing often is to add a reference to the main view inside the secondary view. I mean that Map is the main frame, and it sometimes shows the second view EntityDetails. So I add a reference to Map in the EntityDetailsListener, if necessary of course.

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;
    private Map mapView;
    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
        if (e.getSource() == changeNameButton){
            //For exemple, if you change the name of the entity on the EntityDetails view.
            mapView.updateEntity(this); //calling the update method in the Map class.
        }
    }
}

2) Model autochanging

When your model is changing by its own, your view has to be notified. Your controller, lying between them, is the object receiving notifications from the model to say "Hey, I changed, let's update the view please !"

That's why you should code some fireXXXChange() methods in the Model objects. They will tell listeners they have to do some works.

So you need to pass your listener to the model object. Because you create it before anything else, you have to pass it through a method like addMapListener(MapListener listener). Then, you can notify this controller when data changed.

Because it is quite complicated to expose, I just let you this Oracle tutorial on how to manage MVC with Swing.


I really hope it is not confuse and that I helped you a little bit ! The best way for you to solution your spaghetti code is to document and see what is the best for each application you need to code, because each case is different.

Documentation : - javaworld.com : MVC meets Swing - link-intersystems : MVC pattern implemented with Swing - oracle.com : Java application design with MVC

like image 118
Kapcash Avatar answered Oct 19 '22 16:10

Kapcash