When developing a Java Swing application, the MVC (Model View Controller) principle can sometimes become blurred. Without proper care, and experience, everything can end up holding references to everything else and the Controller risks being a Vishnu, where all code is centrally managed and the Model/Controller blur into one.
In order to keep the MVC pattern crystal clear, and promote clean code, some principles must be adhered to when writing user interfaces. This note gathers some of those principles together:
- Often an UI Specific Model must be created which has some visual meaning, but is not relevant to the backend. In terms of UI design, this UI Specific Model as as much a part of the Model as the backend - so don't try to treat them differently. (e.g. a social network Graph object, which is an alien concept to the backend database but is still a Model from the perspective of the View).
- The majority of the Model should be independent of the Controller. Both the Model and the Controller should be completely independent of any View implementation (see note 3). A good way to enforce this is to move the View to a separate project which depends on the Model and Controller (which can be one project).
- Make Views be Listeners to changes in the Model by registering them with the Controller or Model (Lombok-pg's
@ListenerSupport annotation is great for this). This way, the controller doesn't need to remember who needs to see what - views just register for what is relevant to them.
- When creating a Listener interface, it usually makes more sense to receive atomic change events. If the View really needs to see the full object as well, then perhaps it is worthwhile giving it access to the Controller (with a getter for the relevant Model object) or a reference to the object (if it is final in the Controller).
- Don't be afraid to let the View signatures include Model classes: the point is not to isolate the View and Model but to ensure the Controller is responsible for retrieving and changing the Model. But have the discipline not to directly change the Model from the View code (give copies of the Model to the View if you are paranoid).
- Try to avoid giving a Controller reference to Views which only render information (i.e. which have no need to control).
- The Controller is not there to "control other UI aspects" such as making windows popup - keep UI logic in the View.
- The Controller is there to control the model. A sanity check of the API should reveal lots more "doSomething"s and "addSomethingListener"s rather than "get/setSomething"s.
- Java Swing is single threaded, so don't fuss too much about thread safety in the View - use the JavaBean pattern to keep the code clean (Lombok makes this a breeze) instead of requiring everything at construction.
- To avoid circular references, a respectable instantiation order is:
- create Model (could be as simple as a database connection)
- create Controllers
- register Controllers with each other (they can be listeners too)
- register Model with Controller
- create View
- register View as Listener to changes in the Model/Controller (top level applications might need to do quite a lot of registers)
- register Controller with Views that need Control over the Model
- Use the Netbeans GUI Editor to save time, but stick to core Layouts instead of modern Layouts designed for GUI Editors: they result in weird transparency behaviours and are not truly portable. What you want is usually best achieved with an aggregate of JPanels using appropriate Layouts.
- Learn the latest Swing Extension libraries (e.g. SwingX) as well as the core library.
repaint() tells Swing that an area of the window is dirty; revalidate() tells the layout manager to recalculate the layout and should cause children of the panel to repaint, but may not cause the panel itself to do so.
- There is no consensus if a setter should imply that the Component revalidates or repaints itself. Try to be internally consistent, but do whatever you feel is best. Something to consider is that with Lombok, it might keep code cleaner by not customising setters.
- Most LayoutManagers completely ignore the setXXSize methods, so learn which ones actually make use of these values. Yeah, this really sucks.
- Lombok's @BoundSetter makes it really easy to register a Component with itself to receive propertyChange(event), allowing for pre/post hooks to property changes. This is an excellent place to register subcomponents and controllers with each other.