January 31, 2012

How to install Oracle JDK on Ubuntu 11 from repository.

With Oracle the acquisition of Sun the Oracle have retired the "Operating System Distributor License for Java" (JDL) and the only available JDK release will be the OpenJDK. I had previously some bad experience with the OpenJDK and I prefer to use the official Oracle version of JDK, but I don't want to install the JDK binaries manually – we are using Ubuntu! But the webupd8.org team have been nice to published the binaries in a Personal Package Archives (PPA) and so far I have no problem using it, so I can warmly recommend using it.

To install:
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo mkdir -p /usr/lib/mozilla/plugins #just in case, this will be added to the package in the next version
$ sudo apt-get install oracle-jdk7-installer

And to uninstall:
$ sudo apt-get remove oracle-jdk7-installer


And to verify your installation:
$ java -version
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) 64-Bit Server VM (build 22.0-b10, mixed mode)

And to verify the web browser installation:
http://java.com/en/download/installed.jsp
Verified Java Version
Congratulations!
You have the recommended Java installed (1.7.0_02).

And with maven 2:

$ mvn -version
Java version: 1.7.0_02
Java home: /usr/lib/jvm/java-7-oracle/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux" version: "3.0.0-15-generic" arch: "amd64" Family: "unix"

Java Web Start

The only thing that did not work out of the box was the Java Web Start. To fix that open JNLP file link and when asked for application, brows to:
/usr/lib/jvm/java-7-oracle/bin/javaws

For complete reference see:
http://www.webupd8.org/2012/01/install-oracle-java-jdk-7-in-ubuntu-via.html

How to Make a Custom Launcher (Shortcut) in Ubuntu 11.10 (Oneiric Ocelot) Dock

In my previous blog I wrote about how to create a custom launcher to the Unity dock, but when you upgrade to 11.10 (Oneiric Ocelot) that no longer holds. When you right click on the desktop the menu item Create Launcher... is no longer there.

The simplest way I have yet found is to manually create a desktop file anywhere one the file system, but I would recommend to put it in the same folder as the program you are creating to shortcut to.

Example of my eclipse.desktop:
$[Desktop Entry]
Exec=/home/magnus/bin/eclipse-jee-3.7.1/eclipse
Icon=/home/magnus/bin/eclipse-jee-3.7.1/icon.xpm
Name=Eclipse JEE 3.7.1
StartupNotify=true
Terminal=false
Type=Application
X-KDE-SubstituteUID=false

How to implement MVC (Model View Controller) Pattern with Swing – Part 3

In my third blog about implementing MVC pattern in Swing I will look at how to implement global actions, context sensitive global actions, updating status bar and popup dialogs. But before delving into these issues and I would like again to repeat each class responsibility in the MVC pattern:

Model:
  1. Simple POJO model.
View:
  1. Layout out the Swing components.
  2. Responds to user action by sending request to Control.
  3. Responds to Controller responds.
Controller:
  1. Receives request from the View.
  2. Do logic.
  3. Sends Responds to View(s).
And a last reminder the Request and Responds to and from the Controller should be TOTALLY view technique neutral as the Model. The view specific code, in our case Swing code, ends in the view. Handling all Swing code is the responsibility of each view.

In my previous blog we have seen that each View is only responsible of its Swing component and what happens in other view is it totally unaware of. Try to think of separation of concern. It is the responsibility of the Controller to call the Response methods in each Views that should be updated.

Example:
    private class CreateDocumentAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        public CreateDocumentAction() {
            putValue(Action.NAME, "Create");
            putValue(Action.SHORT_DESCRIPTION, "Create Document");
            putValue(Action.SMALL_ICON, imageIcon("/org/tango-project/tango-icon-theme/16x16/actions/document-new.png"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            getDossierController().preCreateDocument(getModel());
        }
    }


And the corresponding Controller code:
    public void preCreateDocument(Dossier dossier) {
        // 1. do server logic
        Document document = new Document();
        document.setDossierId(dossier.getDossierId());
        // 2. do swing response
        getMainFrame().getView(DocumentModelView.class).setModel(document);
        getMainFrame().getView(DossierTreeModelView.class).addDocumentNode(document);
    }


The Toolbar is yet Another View

So now lets start with looking how to add a JToolBar to our Swing client. If you think of it for a while you will realize that the toolbar is yet another view. So lets write a new class that extends from our AbstractView.

package se.msc.mvcframework.demo.view;

import static se.msc.mvcframework.ComponentFactory.button;
import static se.msc.mvcframework.ComponentFactory.imageIcon;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JToolBar;

import se.msc.mvcframework.AbstractFrame;
import se.msc.mvcframework.AbstractView;
import se.msc.mvcframework.demo.controller.DossierController;

public class ToolBarView extends AbstractView<jtoolbar> {
    private JButton saveButton;
    private Action saveAction;

    public ToolBarView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JToolBar layout() {
        JToolBar toolBar = new JToolBar();
        toolBar.add(button(new OpenDossier()));
        toolBar.add(saveButton = button(saveAction = new SaveAction()));
        return toolBar;
    }

    // ---------- Request Code Goes Here

    public class OpenDossier extends AbstractAction {
        private static final long serialVersionUID = 1L;

        public OpenDossier() {
            putValue(Action.NAME, "Open");
            putValue(Action.SHORT_DESCRIPTION, "Open Dossier");
            putValue(Action.SMALL_ICON, imageIcon("/org/tango-project/tango-icon-theme/16x16/actions/document-open.png"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(DossierController.class).retrieveDossierView();
        }
    }

    public class SaveAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        public SaveAction() {
            putValue(Action.DEFAULT, AbstractFrame.GLOBAL_SAVE_ACTION);
            putValue(Action.NAME, "Save");
            putValue(Action.SHORT_DESCRIPTION, "Save");
            putValue(Action.SMALL_ICON, imageIcon("/org/tango-project/tango-icon-theme/16x16/actions/document-save.png"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
        }
    }

    // ---------- Response Code Goes Here

    public void setGlobalActions(Action[] actions) {
        resetGlobalActions();
        for (Action action : actions) {
            if (AbstractFrame.GLOBAL_SAVE_ACTION.equals(action.getValue(Action.DEFAULT)))
                saveButton.setAction(action);
        }
    }

    public void resetGlobalActions() {
        saveButton.setAction(saveAction);
    }
}


I have here used javax.swing.Action for base class, since they contain all graphical properties such icon, label and tooltip. But also javax.swing.Action can be used for JButton and in JPopupMenu and JMenuItem.

And I have also deliberately not created a abstract class for our action, which could have loaded our images, since I do not believe that such extra abstraction class will not bring any extra to our code nor reduce the number of lines. It will only make our client code more less understandable. But what would justify an extra abstraction layer is if abstract action class called the Controller in thread safe ways and change cursor to busy and in case of failure show a generic exception dialog. That would really bring something to our code, but not loading icons, using Action is quite straightforward and I like to see directly what my code does.

Handling Context Sensitive Global Actions

So after realized that the toolbar is just another view, lets move onto how to make the toolbar context sensitive, i.e. when switching active panel/window the save actions is replaced with the active panel/window save action. But before we must first decide where the concrete Swing save action should be located. It clearly belong to the concrete view. Think again of separation of concern. But what we must do is to expose the global actions so we can send them to the toolbar view. And lastly where do we wire the views together? In the Controller of course.

Let first add an extra method in our abstract view:
    public Action[] getGlobalActions() { return new Action[0]; }


Then in our controller we wire the global actions from the view to the toolbar view.
    public Action[] getGlobalActions() { return new Action[0]; }


And in our toolbar view:

    public void preCreateDocument(Dossier dossier) {
        // 1. do server logic
        Document document = new Document();
        document.setDossierId(dossier.getDossierId());
        // 2. do swing response  
getMainFrame().getView(ToolBarView.class).setGlobalActions(getMainFrame().getView(DocumentModelView.class).getGlobalActions());
        getMainFrame().getView(DocumentModelView.class).setModel(document);
        getMainFrame().getView(DossierTreeModelView.class).addDocumentNode(document);
    }


The last thing we need is to do is to switch back to the default when changing to other views. Here it can be justified to introduce some kind of View lifecycle mechanism, so the programmer does not have to concern about setting the correct toolbar action each time a new View is retrieved.

Managing a Statusbar

After thinking the GUI as a composition of different views, it should not be surprising to think of the statusbar as yet another view as well. And where is the respond sent to update this view. In the Controller of course. Here is a simple example how to implement a statusbar with swinglabs JXStatusBar.

se.msc.mvcframework.demo.view.StatusBarView
package se.msc.mvcframework.demo.view;

import javax.swing.JLabel;

import org.jdesktop.swingx.JXStatusBar;

import se.msc.mvcframework.AbstractFrame;
import se.msc.mvcframework.AbstractView;

public class StatusBarView extends AbstractView<jxstatusbar> {
    private JLabel statusLabel1;
    private JLabel statusLabel2;

    public StatusBarView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JXStatusBar layout() {
        statusLabel1 = new JLabel("Ready1");
        statusLabel2 = new JLabel("Ready2");

        JXStatusBar statusBar = new JXStatusBar();
        statusBar.add(new JLabel(), new JXStatusBar.Constraint(JXStatusBar.Constraint.ResizeBehavior.FILL));
        statusBar.add(statusLabel1, new JXStatusBar.Constraint(100));
        statusBar.add(statusLabel2, new JXStatusBar.Constraint(100));
        return statusBar;
    }

    // ---------- Request Code Goes Here

    // ---------- Response Code Goes Here

    public void setStatusText1(String statusText1) {
        statusLabel1.setText(statusText1);
    }

    public void setStatusText2(String statusText2) {
        statusLabel2.setText(statusText2);
    }
}


Managing Modular Popup Windows

The modular popup windows is also yet another view. The only difference it needs a parent JFrame to show from. And the solution to that is simple. Each View has a reference to the AbstractFrame that holds the JFrame via getFrame().

January 23, 2012

How to Merge PDF Files in Ubuntu

There are so many good web article that you would like to read and sometimes they even stretch over several web pages, but what if you are not online or like to concatenate several different article of your choosing?

One way to do it, it to use the build in print to PDF file in Ubuntu. But what if there are several article? Then you probably want to merge these PDF files. In Ubuntu that is an easy task.

First install pdftk:
$ sudo apt-get install pdftk


Then type the following in the the directory where all the pdf files are:
$ pdftk 1.pdf 2.pdf 3.pdf cat output merged.pdf


For a complete reference see http://ubuntuhowtos.com/howtos/merge_pdf_files.

January 21, 2012

How to Make a Launcher (Shortcut) in the New Ubuntu 11.04 (Natty Narwhal) Dock.

I have been using Ubuntu for some time for writing Java code and for that I use Eclipse. You can install Eclipse via Ubuntu repository, but you probably don't wont to do that because the version in the Ubuntu repository is so old. And installation of Eclipse is so easy, simply download the tar archieve and decompress it to any folder of your liking and then duoble click the eclipse executable.

But after that you probably want a Launcher in the Ubuntu dock? But how do you do that? There is a build in plus icon application for that, but in that program you you can only use installed program, not random program simply unzipped on your disk.

So to make a arbitrary launcher:

1. Right click on you desktop and select 'Create Launcher...'.



2. Type in Name, Command path and optional Comment.
3. And finally click on the default icon and brows to your eclipse home folder and select icon.xpm.
4. Then move the Launcher file to ~/.local/share/applications.
5. And finally drag the Layncher file from ~/.local/share/applications and drop it on the Ubuntu dock.

January 20, 2012

How to implement MVC (Model View Controller) Pattern with Swing – Part 2

In my previous blog I lay the ground of the definition of the MVC pattern and showed you have to implement it with Swing technology. I also defined a minimum set of classes that will help you to separate the client code into the MVC components – View, Controller and Model.
In this blog I will implement a more complex Swing client to show you that the minimum set of framework classes still holds for upholding the separation of concern between the View, Controller and Model. And the MVC pattern really deliver a component based architecture. That is even if you decide to rearrange the views, that will not impose the previous written code of the views, you merely need to rewire the code in the controller. And maybe foremost the code will be clean and what I hope most, will be easy read and follow.

But first lets repeat the responsibilities of each class in the MVC pattern

View
Layout the Swing components.
Sends user action request to the Controller.
Updates the View from Controller responses.
Controller
Do business logic.
Sends responses to views.
Model A POJO.

And a few warnings:

Don't do any logic in the Swing Action, e.g. open other dialogs or frames, call for update in other views. All these code will only destroy the maintainability of you application, because what it is breaking the law of seperation of concern. A specific View should not be aware and shoould not care what other Views are doing. What the View should do is merely pass the user action to the Controller and it is the job of the Controller to decide what to do with the request.

For example. The Controller X recieves a request, does some logic, such as calling the Server Facade and recieves the responds. Call for update on View Y, Z and E and open a new dialog.

By keeping the swing logic in the Controller and also keeping the Controller free from Swing code, it will be easy to go back later to understand the logic and flow in the client, but also for other to read and finally to maintain. It will only be in one place you need to change your code if the working flow or logic changes.

So now lets discuss the example. It is a internal frame solution, that only got one internal frame a dossier window. The dossier window contains document and present these structure in a tree. The detail of each tree node is shown in the same internal frame but in a detail panel. Here is a snapshot of the example.



Lets start with Main class.
1. It creates and saves all Views and Controllers in HashMap that the base class holds, so that all Views and Controller will be accessible through the entire client.
2. Lay out the JFrame with a default view.

package se.msc.mvcframework.demo.main;

import static se.msc.mvcframework.ComponentFactory.frame;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;

import se.msc.mvcframework.AbstractFrame;
import se.msc.mvcframework.InternalFrameView;
import se.msc.mvcframework.demo.controller.DossierController;
import se.msc.mvcframework.demo.view.DocumentFormView;
import se.msc.mvcframework.demo.view.DossierFormView;
import se.msc.mvcframework.demo.view.DossierTreeView;
import se.msc.mvcframework.demo.view.DossierView;

public class MainInternalFrame extends AbstractFrame {
    private JDesktopPane desktopPane;

    @Override
    protected void registerAllViews() {
        desktopPane = new JDesktopPane();
        views.put(DossierFormView.class, new DossierFormView(this));
        views.put(DocumentFormView.class, new DocumentFormView(this));
        views.put(DossierTreeView.class, new DossierTreeView(this));
        views.put(DossierView.class, new InternalFrameView<dossierview>(this, desktopPane, new DossierView(this)));
    }

    @Override
    protected void registerAllControllers() {
        controllers.put(DossierController.class, new DossierController(this));
    }

    @Override
    protected JFrame layout() {
        return frame("Demo MVC Framework", desktopPane, new ToolBarView(this).getContentPane());
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new MainInternalFrame().show();
            }
        });
    }
}


We have two form views that have simply to to important methods that populates the view – getValues and setValues.

Now for the Tree View. This View is more complex but only because Swing is so verbose. I will leave the detail of how to implement a modifiable tree you can download the source at the end of the blog or google. I will in upcoming blog present a better ways to decorate the existing swing components, to take a POJO Model and populate from that. And to hold that POJO Model, so you can later ask the swing component for a POJO Model instead of making tiring calling of get and set from the model to the swing component and vice versa. But showing you simplifying handling of swing components is out of the scoop of this blog. The import thing is to show you how to seperate the concern of the Views and Controllers and the interaction between them.

    // ------------ Request Code Goes Here
    
    @SuppressWarnings("unchecked")
    private <b> BeanTreeNode<b> getSelectedNode(Class<b> nodeClass) {
        return (tree.getSelectionPath() != null) ? (BeanTreeNode<b>) tree.getSelectionPath().getLastPathComponent() : null;
    }
    
    public class TreeMouseListener extends MouseAdapter {
        
        @Override
        public void mousePressed(MouseEvent e) {
            BeanTreeNode<object> node = getSelectedNode(Object.class);
            if (node == null) return;            
            if (e.isPopupTrigger()) documentPopupMenu.show((JComponent) e.getSource(), e.getX(), e.getY());
        }
        
        @Override
        public void mouseReleased(MouseEvent e) {
            BeanTreeNode<object> node = getSelectedNode(Object.class);
            if (node == null) return;            
            if (e.isPopupTrigger()) documentPopupMenu.show((JComponent) e.getSource(), e.getX(), e.getY());
        }
        
        @Override
        public void mouseClicked(MouseEvent e) {
            BeanTreeNode<object> node = getSelectedNode(Object.class);
            if (node == null) return;
            if (node.getBean() instanceof Dossier) getDossierController().retrieveDossier((Dossier) node.getBean());
            if (node.getBean() instanceof Document) getDossierController().retrieveDocument((Document) node.getBean());
        }
    }

    public class CreateDocument extends AbstractAction {
        private static final long serialVersionUID = 1L;

        public CreateDocument(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            getDossierController().PreCreateDocument();
        }
    }

    public class DeleteDocument extends AbstractAction {
        private static final long serialVersionUID = 1L;

        public DeleteDocument(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            BeanTreeNode<object> node = getSelectedNode(Object.class);
            if (node.getBean() instanceof Document) getDossierController().DeleteDocument((Document) node.getBean());
        }
    }

    // ------------ Response Code Goes Here
    
    public void setDossier(Dossier dossier) {
        dossierNode.setBean(dossier);
    }

    public void addDocumentNode(Document document) {
        dossierNode.getBean().addDocument(document); // update views internal cache    
        BeanTreeNode<document> documentNode = new BeanTreeNode<document>(document, "Name", null);
        dossierNode.add(documentNode);
        treeModel.nodeStructureChanged(dossierNode);
        tree.setSelectionPath(new TreePath(documentNode.getPath()));
    }

    public void updateDocumentNode(Document document) {
        BeanTreeNode<document> documentNode = getSelectedNode(Document.class);
        documentNode.setBean(document);
        treeModel.nodeStructureChanged(documentNode);
        tree.setSelectionPath(new TreePath(documentNode.getPath()));
    }

    public void deleteDocumentNode(Document document) {
        dossierNode.getBean().removeDocument(document); // update views internal cache
        dossierNode.remove(getSelectedNode(Document.class));
        treeModel.nodeStructureChanged(dossierNode);
        tree.setSelectionPath(new TreePath(dossierNode.getPath()));
    }


As you can see in the code above the the tree View does not do anything in theirs action, just merely call the correct Controller method.

And now the Controller where we wire everything together.

package se.msc.mvcframework.demo.controller;

import java.util.Random;
import java.util.UUID;

import se.msc.mvcframework.AbstractController;
import se.msc.mvcframework.AbstractFrame;
import se.msc.mvcframework.demo.model.Document;
import se.msc.mvcframework.demo.model.Dossier;
import se.msc.mvcframework.demo.view.DocumentFormView;
import se.msc.mvcframework.demo.view.DossierFormView;
import se.msc.mvcframework.demo.view.DossierTreeView;
import se.msc.mvcframework.demo.view.DossierView;

public class DossierController extends AbstractController {

    public DossierController(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    public void showDossierView() {
        // 1. do server logic
        Dossier dossier = new Dossier();
        dossier.setDossierId(new Random().nextLong());
        dossier.setDossierNumber(UUID.randomUUID().toString());
        dossier.setTitle("My Dossier");
        // 2. do swing response  
        getMainFrame().getInternalFrameView(DossierView.class).setTitle(dossier.getTitle());
        getMainFrame().getView(DossierTreeView.class).setDossier(dossier);
        getMainFrame().getInternalFrameView(DossierView.class).show();
    }

    public void retrieveDossier(Dossier dossier) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getView(DossierFormView.class).setValues(dossier);
        getMainFrame().getInternalFrameView(DossierView.class).getView().showDossierFormView();
    }
    
    public void retrieveDocument(Document document) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getView(DocumentFormView.class).setValues(document);
        getMainFrame().getInternalFrameView(DossierView.class).getView().showDocumentFormView();
    }
    
    public void PreCreateDocument() {
        // 1. do server logic
        Document document = new Document();
        // 2. do swing response
        getMainFrame().getView(DossierTreeView.class).addDocumentNode(document);
        getMainFrame().getView(DocumentFormView.class).setValues(document);
        getMainFrame().getInternalFrameView(DossierView.class).getView().showDocumentFormView();
    }

    public void cancelCreateDocument(Document document) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getView(DossierTreeView.class).deleteDocumentNode(document);
        getMainFrame().getInternalFrameView(DossierView.class).getView().showDossierFormView();
    }
    
    public void CreateUpdateDocument(Document document) {
        // 1. do server logic
        if (document.getDocumentId() == null) {
            document.setDocumentId(new Random().nextLong());
            document.setDocumentNumber(UUID.randomUUID().toString());
        }
        // 2. do swing response        
        getMainFrame().getView(DocumentFormView.class).setValues(document);
        getMainFrame().getView(DossierTreeView.class).updateDocumentNode(document);
    }
    
    public void DeleteDocument(Document document) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getView(DossierTreeView.class).deleteDocumentNode(document);
        getMainFrame().getInternalFrameView(DossierView.class).getView().showDossierFormView();
    }
}


I hope by just reading the code in the Controller you will get the feeling what will happen. But the best part. Everything is type safe! You can click on the method and directly go the code!


The complete source code from https://sourceforge.net/projects/swingframework/files/.

January 17, 2012

How to implement MVC (Model View Controller) Pattern in Swing

The MVC pattern is the most common pattern when it comes to web framework, but when building a rich client with swing or by using a rich client framework such as, Eclipse Rich Client Platform, Eclipse RCP , or Spring Rich Client Project, Spring RCP or trying to develop a similar platform yourself, thing tends to fast get very complicated. Why is that? Well, first Swing is very low tech. To do even the most simple thing requires quite a lot of lines of code. Another reason is that a lot of people have not taken their time to fully understand the MVC pattern. And maybe last there are a lot of misunderstanding what a view is in the MVC pattern and the Swing model. The Swing model is entire a presentation necessity and therefore only belong to the view.

So hopefully what I will achieve in this blog is to explain the MVC pattern and how to implement it with Swing. What I will not do in this blog is to explain how to use certain Swing components, their are quite a few tutorials out there, e.g. Oracle own Swing Tutorial, http://docs.oracle.com/javase/tutorial/uiswing/.

Last start with explaining the MVC pattern fundamental. Maybe the easiest way to explain MVC is to look at how things work in the web world.


What happens:
  1. The View builds the graphical interface and reacts to user interaction, by redirecting the request to the Controller.
  2. The Controller respond to the request, do logic and sends response to a view.
  3. The View takes these parameters and presents them in a view to the user.
And as you already might have guessed the above request and response parameters is the Model in the MVC pattern.

Before continue I would like to stress a few things extra hard:
  • All graphical components are created and layed out in the view. Nowhere else!
  • The Controller is neutral to whatever graphical user interface technique used! A simple control question is. Think that you must change graphical technique, from example Swing to SVT. Will you controller classes be affected? This question might feel quite theoretical but is a good eye opener if you have successfully separated the view concern from the controller concern.
  • The Model should also be plain old Java object, POJO, and not infected with presentation specific codes. And again check that by asking yourself if you were forced to change presentation technique. How would that affect your model classes?
And a final reminder about the model in the MVC pattern and the model in Swing. The Swing model should not be confused with the model in the MVC pattern. The Swing model is entirely part of the choosen presentation technique, i.e. Swing and belongs only in the view and should not be mixed with the model in the MVC pattern.

The last thing before diving into concrete code is the concern of Object Instances Lifecycle. Now you might start to wonder what that has to do with MVC. And the question is none, but the problem is still real and I think one must adress this with some thinking and strategy, because it is so vital to the application and programmers daily life. And not to mention testability. Which is something I will not go into any deeper in this blog, but I can recommned the FEST test framework. Anyhow which Swing test framework you choose you must before have a clear lifecycle handle of object in your rich client. Otherwise you will end up with untestable code.

So lest start with the View. What does a view do?
  1. Layout the graphical components.
  2. Responds to user interaction (and sends the request to the Controller).
  3. Updates the view (requested from the Controller).
Lets starts with the layout.

package se.msc.examples.mvcframework;

import javax.swing.JComponent;

public abstract class AbstractView<c extends JComponent> {
    private final AbstractFrame mainFrame;
    private final C contentPane;

    public AbstractView(AbstractFrame mainFrame) {
        this.mainFrame = mainFrame;
        this.contentPane = layout();
    }

    protected abstract C layout();

    protected AbstractFrame getMainFrame() {
        return mainFrame;
    }

    public C getContentPane() {
        return contentPane;
    }
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    // ------------ Response Code Goes Here

    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }   
}


Try not to bother about the static component methods, such as frame, etc. they are all methods in a ComponentFactory that do all the Swing plumbing construction. I will later show them in the Appendix.

But our application will certaintly contains several views, so lets we refactor out the main method in a class thats holds the JFrame instance. What we also will do in our frame class is to solve the problem of object lifecycle and dependencies to those. We will create a map that hold all the views and another that holds the controllers. And finally we will inject the instance of the main frame to all views and controllers. In this way, we will have only one instance of all the views and controllers, but also they will be accessable everywhere.

package se.msc.examples.mvcframework;

import static se.msc.examples.mvcframework.ComponentFactory.showFrame;

import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public abstract class AbstractFrame {
    protected final JFrame frame;
    protected final Map<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>> views = new HashMap<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>>();
    protected final Map<Class<? extends AbstractController>, AbstractController> controllers = new HashMap<Class<? extends AbstractController>, AbstractController>();

    public AbstractFrame() {
        registerAllViews();
        registerAllControllers();
        this.frame = layout();
    }

    protected abstract void registerAllViews();

    protected abstract void registerAllControllers();

    protected abstract JFrame layout();

    protected void show() {
        showFrame(frame);
    }

    @SuppressWarnings("unchecked")
    public <v extends AbstractView<? extends JComponent>> V getView(Class<v> viewClass) {
        return (V) views.get(viewClass);
    }

    @SuppressWarnings("unchecked")
    public <v extends AbstractView<JPanel>> InternalFrameView<v> getInternalFrameView(Class<v> viewClass) {
        return (InternalFrameView<v>) views.get(viewClass);
    }

    @SuppressWarnings("unchecked")
    public <c extends AbstractController> C getController(Class<c> controllerClass) {
        return (C) controllers.get(controllerClass);
    }
}


package se.msc.examples.demo.main;

import static se.msc.examples.mvcframework.ComponentFactory.frame;

import javax.swing.JFrame;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.mvcframework.AbstractFrame;

public class MainFrame extends AbstractFrame {

    @Override
    protected void registerAllViews() {
        views.put(PersonListView.class, new PersonListView(this));
    }

    @Override
    protected void registerAllControllers() {
        controllers.put(PersonController.class, new PersonController(this));
    }

    @Override
    protected JFrame layout() {
        return frame("Demo MVC Framework", getView(PersonFormView.class).getContentPane(), null);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MainFrame().show();
            }
        });
    }
}


Now lets get back to the View and add code for the user interaction request.

package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    private class CreatePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).preCreatePerson();
        }
    }

    private class RetrievePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).retrievePerson(person);
        }
    }

    private class DeletePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).deletePerson(person);
        }
    }

    // ------------ Response Code Goes Here

    public void addPerson(Person person) {
        tableModel.insert(person);
    }

    public void updatePerson(Person person) {
        tableModel.setBean(table.getSelectedRow(), person);
    }

    public void deletePerson() {
        tableModel.remove(table.getSelectedRow());
    }
    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }    
}


As you can see, it is easy to get instance references to the controller class, but the controller is also used in a type safe way. This approach have several advances compared with external configuration files where you wire views and controllers together through weak string references. What will happen if decide to refactor and change a controller method name?

The view can in the same manner be accessed in the same type safe way. To need for external configuration files, such as XML files.

So this is the basic of MVC. Now look if this holds for more complex GUI. Lets say we want to make a desktop application. Well the layout stays the same, but we need to put theirs panel in internal frame. We could do that in the view, but what happens if requirement changes and these views wants to put in a tabbed panel instead. No lets leave the views intact and instead lets created a decorating view that takes a view of panel as argument.

package se.msc.examples.mvcframework;

import static se.msc.examples.mvcframework.ComponentFactory.internalFrame;

import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;

public class InternalFrameView<v extends AbstractView<JPanel>> extends AbstractView<jinternalframe> {
    private final JDesktopPane desktopPane;
    private final V view;
    private final JInternalFrame internalFrame;

    public InternalFrameView(AbstractFrame mainFrame, JDesktopPane desktopPane, V view) {
        super(mainFrame);
        this.desktopPane = desktopPane;
        this.view = view;
        this.internalFrame = internalFrame(view.getContentPane(), view.getClass().getSimpleName());
    }

    @Override
    protected JInternalFrame layout() {
        return internalFrame;
    }

    // ------------ Request Code Goes Here

    // ------------ Response Code Goes Here

    public V getView() {
        return view;
    }

    public void show() {
        desktopPane.add(internalFrame);
        internalFrame.pack();
        internalFrame.setVisible(true);
        try {
            internalFrame.setSelected(true);
        } catch (java.beans.PropertyVetoException e) {
        }
    }

    public void close() {
        internalFrame.dispose();
        desktopPane.remove(internalFrame);
    }
}


Then we need to modify our main frame class.

package se.msc.examples.demo.main;

import static se.msc.examples.mvcframework.ComponentFactory.frame;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.demo.view.PersonTreeView;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.InternalFrameView;

public class MainInternalFrame extends AbstractFrame {
    private JDesktopPane desktopPane;

    @Override
    protected void registerAllViews() {
        desktopPane = new JDesktopPane();
        views.put(PersonListView.class, new InternalFrameView<personlistview>(this, desktopPane, new PersonListView(this)));
        views.put(PersonFormView.class, new InternalFrameView<personformview>(this, desktopPane, new PersonFormView(this)));
    }

    @Override
    protected void registerAllControllers() {
        controllers.put(PersonController.class, new PersonController(this));
    }

    @Override
    protected JFrame layout() {
        return frame("Demo MVC Framework", desktopPane, new ToolBarView(this).getContentPane());
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new MainInternalFrame().show();
            }
        });
    }
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    private class CreatePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).preCreatePerson();
        }
    }

    private class RetrievePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).retrievePerson(person);
        }
    }

    private class DeletePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).deletePerson(person);
        }
    }

    // ------------ Response Code Goes Here

    public void addPerson(Person person) {
        tableModel.insert(person);
    }

    public void updatePerson(Person person) {
        tableModel.setBean(table.getSelectedRow(), person);
    }

    public void deletePerson() {
        tableModel.remove(table.getSelectedRow());
    }
    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }    
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.label;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.textField;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;

import javax.swing.JPanel;
import javax.swing.JTextField;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.ButtonBarFactory;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class PersonFormView extends AbstractView<jpanel> {
    private JTextField id;
    private JTextField created;
    private JTextField name;
    private Person person;

    public PersonFormView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    // ------------ Request Code Goes Here

    @Override
    protected JPanel layout() {
        FormLayout formLayout = new FormLayout("right:pref, 2dlu, pref:grow", // columns
                "pref, 3dlu, pref, 3dlu, pref, 3dlu, pref"); // rows
        PanelBuilder builder = new PanelBuilder(formLayout);
        builder.setDefaultDialogBorder();
        CellConstraints cc = new CellConstraints();

        builder.add(label("Id:", null), cc.xy(1, 1));
        builder.add(id = textField(10, false), cc.xy(3, 1));

        builder.add(label("Created:", null), cc.xy(1, 3));
        builder.add(created = textField(10, false), cc.xy(3, 3));

        builder.add(label("Name:", null), cc.xy(1, 5));
        builder.add(name = textField(10, true), cc.xy(3, 5));

        JPanel pnlSaveCancelButtons = ButtonBarFactory.buildOKCancelBar(button("Save", new Save()), button("Cancel", new Cancel()));
        builder.add(pnlSaveCancelButtons, cc.xy(3, 7, "left, center"));

        return builder.getPanel();
    }

    private class Cancel implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).cancelCreatePerson();
        }
    }

    private class Save implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).savePerson(getValues());
        }
    }

    // ------------ Response Code Goes Here

    public Person getValues() {
        person.setName(name.getText());
        return person;
    }

    public void setValues(Person person) {
        this.person = person;
        if (person.getId() != null) id.setText(new String(person.getId()));
        else id.setText("");
        if (person.getCreated() != null) created.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(person.getCreated()));
        else created.setText("");
        if (person.getName() != null) name.setText(new String(person.getName()));
        else name.setText("");
    }

    // ------------ Test Code Goes Here

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonFormView", new PersonFormView(null).getContentPane(), null));
            }
        });
    }
}



package se.msc.examples.demo.controller;

import java.util.Date;
import java.util.UUID;

import se.msc.examples.demo.model.Person;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.mvcframework.AbstractController;
import se.msc.examples.mvcframework.AbstractFrame;

public class PersonController extends AbstractController {

    public PersonController(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    public void savePerson(Person person) {
        if (person.getId() == null) postCreatePerson(person);
        else updatePerson(person);
    }

    // ------------ CRUD Code Goes Here

    public void preCreatePerson() {
        // 1. do server logic
        Person person = new Person();
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).getView().setValues(person);
        getMainFrame().getInternalFrameView(PersonFormView.class).show();
    }

    private void postCreatePerson(Person person) {
        // 1. do server logic
        person.setId(UUID.randomUUID().toString());
        person.setCreated(new Date());
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
        getMainFrame().getInternalFrameView(PersonListView.class).getView().addPerson(person);
    }

    public void cancelCreatePerson() {
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
    }

    public void retrievePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).getView().setValues(person);
        getMainFrame().getInternalFrameView(PersonFormView.class).show();
    }

    private void updatePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
        getMainFrame().getInternalFrameView(PersonListView.class).getView().updatePerson(person);
    }

    public void deletePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonListView.class).getView().deletePerson();
    }
}


Conclusion
MVC greatly reducing the coupling between seperated classes, but doing right is not always easy.

The complete source code from https://sourceforge.net/projects/swingframework/files/.