Java Swing - How to Create a Better Link Between a Model and a View

The biggest problem I encounter when developing desktop applications using Swing is the spaghetti that I get when filling out the application.

I come from the PHP background with MVC, where I had an easy way to implement dependency injection, and I just had access to any layer of the model, when I need it, I just built the necessary classes.

But in Java, this is a little more complicated for me, because Swing components are basically small models that do something, the point is how can I deal with it with a beautiful design without spaghetti.

What do I call spaghetti design?

For me, spaghetti design is where I sometimes get to the point that in my component I need access to another component, or I need access to the place where I don't have it. To do this, I need to transfer these instances from component to component, which means that I create access for these objects for components that will never use it, and are simply used as the person in the middle to transfer the instance. Basically, the moment I ask myself.

Take a look at this dummy example:

You have a Map component. This component should draw you a map of your country and attract entities (the entity may simply be some kind of city mark, military base, radar data and everything that the map can actually display). So, in your model layer, you probably somewhere defined a class called EntitiyManager that contains a list of entities, each object is something that inherits MapEntity . So what do you need to do to attract these objects?

Approach 1

Go EntityManager to Map and retrieve data from EntityManager for drawing:

 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 the EntityManager to the 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 is long and dirty and not organized at all, making your drawing method too large and difficult to complete. Even if you can break it down into methods.

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

But overall, I don’t like the fact that I passed my EntityManager like this, to the view, it just doesn’t look good to me.

Now let me say that I need to create a function that allows me to measure the distance on the map. I need a mouseEventListener , and this mouse event listener should have access to where it can pass input. I need a place to control this distance measure. What if I want to create a component of a small window that shows how many kilometers I have measured, and on the other hand I want to show the dashed line of my measurement on the map, this means that I need to use the measurement distance in two components, these two are connected with friend.

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

Another problem that comes to me every time, I don’t know how I can get my controller to correctly enter input into the model, I can’t find a good way to create a controller in Swing, because the components are developed. So for me, Input Control is also confusing and just makes me mess up my program.

So, what are good approaches to developing the perfect Swing app?

Update

My biggest problem arises when I have a view, and I need to update another view when something has changed in the current view.

Say we have a representation of Map . Map view is a map of your country. When you move the mouse, you get an event called by this mouseMoved or something like that, then you get X and Y mice on this map. Now we need to do

  • Convert X , Y to display the latitude and longitude that are made in the model for this view,
  • We have another view called BottomInformation , which is basically a bar at the bottom of the frame and displays information. We need to update the BottomInformation to show this longitude and latitude that we got.

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

+5
source share
1 answer

I also come across these "spaghetti" when I launch the complex MVC Swing application. This is how I do it. But this is my own way of doing, maybe you will not agree with me! If someone does not agree with any of my words, just let me know. I'm not a professional, just trying to help. :)


Reminder

First, Boris Spider has already introduced you to the good principles of MVC / MVP. I just remember the basics:

MVC Scheme

This means that your controller needs direct access to the model and view objects. The view has access to the model object. The model works on its own. This means that if you change your model (by changing the API used, by changing the algorithms, ...), the view does not care, it just shows it in several ways.

The controller must listen to user actions (buttons, fields, etc.) and modify the model data in accordance with this.

A view can only display data from a model. Nothing more.


How to implement

Instance of objects

First I create a model and view objects.

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

You create your objects by passing the links necessary for each of them through the designer parameters. A model is always the first to be created because it is independent.

Then you create a view that needs a model to get data values.

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

Drawing objects

My approach is that the presentation requires a method for each Entity type. For example, the drawCity() method, then drawRoad() or drawOilStation() . Because representation is the only object that needs to know how to draw them. A model should not deal with it.

Since your example should paint all components, the paintComponents() method will be rather messy, yes. In my opinion, your “approach 1” is the best way to do this.

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

Suppose you have a GUI with some JList and JPanel. Your loading methods (for reading data displayed from the model) will be called separately, each of which receives the data that they need from the model.

For instance:

 public void createInterface(){ //Instanciate some Swing components fillPhoneList(); updateNamePanel(); } private void fillPhoneList(){ List<PhoneNumber> phoneList = model.getPhoneList(); for(PhoneNumber phone : phoneList){ phoneListView.add(phone); } } 

View Update

1) Change the types of user actions

Often the controller responds when an interface event occurs. The dispatcher’s job is to let the model change while updating 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 . Both of them have the corresponding representation object as an attribute. They capture all user actions from the view.

 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 often do is add a link to the main view inside the secondary view. I mean, Map is the main frame, and sometimes it shows a second kind of EntityDetails . Therefore, I am adding a Map link to 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) Autostart model

When your model changes on its own, your opinion should be notified. Your controller, lying between them, is the object receiving notifications from the model to say: "Hey, I changed, let me update the view, please!"

This is why you should encode some fireXXXChange() methods in model objects. They will tell listeners that they need to do some work.

So, you need to pass your listener to the model object. Since you create it first of all, you need to pass it using a method like addMapListener(MapListener listener) . Then you can notify the controller when the data changes.

Since it's pretty hard to expose, I just give you this Oracle tutorial on how to manage MVC with Swing .


I really hope that this does not bother me and that I helped you a bit! The best way to solve your spaghetti code is to document and view what is best for each application that needs to be encoded, because each case is different.

Documentation: - javaworld.com: MVC meets Swing - link-intersystems: MVC pattern implemented using Swing - oracle.com: Developing Java applications with MVC

+5
source

All Articles