MVC Implementation in Java Swing By JFrame & JInternalFrame Approach

MVC Brief

MVC (Model View Controller) is a layer representation to build an application. MVC must separate source code files structure into 3 layers which are Model, View and Controller.
MVC is needed to build large application so that it is easier to maintain and develop. Changes in one layer doesn’t effect to another layers so it is loose coupling.

I will write short explanation about MVC. Model layer is a business logic. It contains entity of a business object i.e customer, product, operation, sales details etc.
Please note that Model is not database access object. Database access is closely related to Model but it is not Model itself. Model uses Database access object to produces business objects.
View layer is simply an UI presentation. Controller layer contains methods to handle user events such as click, keypress, etc. This controller layer sometimes confusing since user events is inside View layer.

Although user events is inside View layer but the handler method itself could be as/in Controller. So controller plays as a bridge between Model & View. It chooses which model object should be accessed and which view sould be presented for that related user event.

To apply complete MVC layers we need 3 source code files at minimum. These 3 represent each one of Model/Controller/View.
I said ‘complete MVC’ because of sometimes application Controller and View combined into one layer only. This simpler M(VC) only separates Model and its View. For this kind of method, it requires 2 files only.
I think most of Swing Application uses this M(VC) layer. View & User events directly access Model objects without a Controller.

Swing MVC with JFrame & JInternaFrame

To think about MVC in Swing, I need to figure out how to separate each one of MVC layers efficiently. I came accross to JFrame & JInternalFrame approach.

Practically, I have main JFrame with child/internal JInternalFrame. Main JFrame acts as a Controller and JInternalFrame is a View. Model represents business logic objects.

Here in this blog post, I will show you a MVC implementation with simple example. This example just shows list of products and also has add new product functionality.
As I said earlier that Main JFrame is a Controller and so between main JFrame & JInternalFrame as View class source code must be separated. Also Model layer off course.

If you want to download my NetBeans Project for build this app then please go to Download Swing MVC Project Source.
However, I will write this projcct inside ‘sourcecode’ post.

Common Class

To communicate the entity object between Model, View, Controller, I need common class that can be accessed by those 3 layers. In this example, I have ‘Product’ common class.
Product.java

package com.wordpress.gugiaji.swing.mvc;

import java.util.ArrayList;
import java.util.List;

public class Product {
    private String name, description;
    private int id;
    
    public Product(int id, String name, String description) {
        set_id(id);
        set_name(name);
        set_description(description);
    }
    
    public void set_id(int id ) {
        this.id = id;
    }
    
    public void set_name(String name) {
        this.name = name;
    }
    
    public void set_description(String description) {
        this.description = description;
    }
    
    public int get_id() {
        return this.id;
    }
    
    public String get_name() {
        return this.name;
    }
    
    public String get_description() {
        return this.description;
    }
    
    public List<String> ColumnNames() {
        List<String> cols = new ArrayList<String>();
        cols.add("ID");
        cols.add("Name");
        cols.add("Description");
        
        return cols;
    }
}

This class has setter & getter of product ID, name & description.

Model Layer

Above Product object instance is usually produced by class in Model layer. Here I only listed products using ArrayList.
Model layer, mdlProduct.java

package com.wordpress.gugiaji.swing.model;

import com.wordpress.gugiaji.swing.mvc.Product;
import java.util.ArrayList;

public class mdlProduct {
    static ArrayList<Product> lstProduct;
    public static void ActivateProduct() {
        lstProduct = new ArrayList<Product>();
        Product prd;            
        prd = new Product(1, "Jet", "Plane");
        lstProduct.add(new Product(1, "Jet", "Plane"));            
        prd = new Product(2, "Ferari", "Car");
        lstProduct.add(new Product(2, "Ferari", "Car"));        
        
    }
    
    public static ArrayList<Product> get_ProductList() {
        return lstProduct;
    }
    
    public static void Add_Product(Product prd) {
        lstProduct.add(prd);
    }    
    
}

Controller Layer
I have Controller class to access Model layer and get its entity object instance or to add data.
ctrlMasterTrans.java

package com.wordpress.gugiaji.swing.controller;

import com.wordpress.gugiaji.swing.model.mdlProduct;
import com.wordpress.gugiaji.swing.mvc.Product;
import java.util.ArrayList;

public class ctrlMasterTrans {
    public static void ActivateProduct() {
        mdlProduct.ActivateProduct();
    }
    
    public static ArrayList<Product> ListOfProduct() {
        return mdlProduct.get_ProductList();
    }
    
    public static boolean Add_Product(int ID, String name, String description) {
        try {
            Product prd = new Product(ID, name, description);
            mdlProduct.Add_Product(prd);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }    
}

Main JFrame as a Controller too has menu for listing products and add new product. Also, it handles user events from View layer. You can see screen shot and codes of Controller form below.

frmController.java

package com.wordpress.gugiaji.swing.controller;

import com.wordpress.gugiaji.swing.view.*;
import com.wordpress.gugiaji.swing.mvc.*;

import java.beans.PropertyVetoException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JInternalFrame;
import java.util.*;

public class frmController extends javax.swing.JFrame {

    public frmController() {
        initComponents();
        ctrlMasterTrans.ActivateProduct();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jDesktopPane1 = new javax.swing.JDesktopPane();
        jMenuBar1 = new javax.swing.JMenuBar();
        mnuMaster = new javax.swing.JMenu();
        masterProduct = new javax.swing.JMenu();
        masProductList = new javax.swing.JMenuItem();
        masAddProduct = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        javax.swing.GroupLayout jDesktopPane1Layout = new javax.swing.GroupLayout(jDesktopPane1);
        jDesktopPane1.setLayout(jDesktopPane1Layout);
        jDesktopPane1Layout.setHorizontalGroup(
            jDesktopPane1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 557, Short.MAX_VALUE)
        );
        jDesktopPane1Layout.setVerticalGroup(
            jDesktopPane1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 383, Short.MAX_VALUE)
        );

        mnuMaster.setText("Master");

        masterProduct.setText("Product");

        masProductList.setText("Product List");
        masProductList.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                masProductListActionPerformed(evt);
            }
        });
        masterProduct.add(masProductList);

        masAddProduct.setText("Add New Product");
        masAddProduct.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                masAddProductActionPerformed(evt);
            }
        });
        masterProduct.add(masAddProduct);

        mnuMaster.add(masterProduct);

        jMenuBar1.add(mnuMaster);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jDesktopPane1, javax.swing.GroupLayout.Alignment.TRAILING)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jDesktopPane1)
        );

        pack();
    }// </editor-fold>                        

    private void masProductListActionPerformed(java.awt.event.ActionEvent evt) {                                               
        try {
			//this method open View Layer object, InternalFrm1 to presented list of products
            ArrayList<Product> lstproduct = ctrlMasterTrans.ListOfProduct();
            
            InternalFrm1 interfrm = new InternalFrm1("Products", lstproduct);
            
            jDesktopPane1.add(interfrm);
            interfrm.setMaximum(true);
            interfrm.setVisible(true);
        } catch (PropertyVetoException ex) {
            Logger.getLogger(frmController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }                                              
    public void ShowProductList() {
        this.masProductListActionPerformed(null);
    }
        
    private void masAddProductActionPerformed(java.awt.event.ActionEvent evt) {                                              
        //this method open View Layer object, InternalFrm2 to show add new product form
        InternalFrm2 interfrm = new InternalFrm2("Add new product");
        jDesktopPane1.add(interfrm);
        interfrm.setSize(260, 200);
        interfrm.setVisible(true);
    }                                             

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(frmController.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(frmController.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(frmController.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(frmController.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        
        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new frmController().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JDesktopPane jDesktopPane1;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenuItem masAddProduct;
    private javax.swing.JMenuItem masProductList;
    private javax.swing.JMenu masterProduct;
    private javax.swing.JMenu mnuMaster;
    // End of variables declaration                   
}

As you can see at above code that when user clicks menu in frmController then View Layer i.e InternalFrm1 or InternalFrm2 will be showed. InternalFrm1/2 are JInternalFrame classes.
Their original class source code file seperated from frmController and so they act as View layer.

View Layer

To show list of products, I have InternalFrm1 object.

InternalFrm1.java:

package com.wordpress.gugiaji.swing.view;

import com.wordpress.gugiaji.swing.mvc.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class InternalFrm1 extends JInternalFrame {
    public InternalFrm1(String title, ArrayList<Product> lstproduct) {
        super(title, true, true, true, true);
        JScrollPane jsp =new JScrollPane() ;
        JTable jtb = new JTable();
        DefaultTableModel dtm;
        dtm = new DefaultTableModel()  {
            @Override
            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };
        
        if (lstproduct.size() > 0) {
            List<String> cols = lstproduct.get(0).ColumnNames();
            
            for (String columnName : cols) {
                dtm.addColumn(columnName);
               
            }

            Vector<String> lst;
           
            for (int i=0; i<lstproduct.size(); i++) {
                lst= new Vector<String>();
                lst.add(String.valueOf(lstproduct.get(i).get_id()));
                lst.add(lstproduct.get(i).get_name());
                lst.add(lstproduct.get(i).get_description());
                
                dtm.addRow(lst);
            }

            
            jtb.setModel(dtm);
        }
        jsp.setViewportView(jtb);
        
        add(jsp);
    }
}

This InternalFrm1 has JTable and its data source comes from Controller which open this InternalFrm1. In this case when user clicks List of product menu from frmController then ArrayList value will be passed to constructor of InternalFrm1. So when InternalFrm1 is showed the list will be displayed automatically.

Second view, Form to add Product, InternalFrm2.java

package com.wordpress.gugiaji.swing.view;

import com.wordpress.gugiaji.swing.controller.ctrlMasterTrans;
import com.wordpress.gugiaji.swing.controller.frmController;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class InternalFrm2 extends JInternalFrame {
    public InternalFrm2(String title) {
        super(title, true, true, true, true);
        initComponents();
    }
    
    private void initComponents() {
        lblID = new JLabel();
        txid = new JTextField();
        lblName = new JLabel();
        txname = new JTextField();
        lblDesc = new JLabel();
        txdesc = new JTextField();
        btnsave = new JButton();

        lblID.setText("ID");

        lblName.setText("Name");

        lblDesc.setText("Description");

        btnsave.setText("Save");
        btnsave.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                btnsave_click(e);
            }
            
        });

        GroupLayout layout = new GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(21, 21, 21)
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING)
                    .addComponent(btnsave)
                    .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
                        .addGroup(layout.createSequentialGroup()
                            .addComponent(lblDesc)
                            .addGap(18, 18, 18)
                            .addComponent(txdesc, GroupLayout.PREFERRED_SIZE, 117, GroupLayout.PREFERRED_SIZE))
                        .addGroup(layout.createSequentialGroup()
                            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                                .addComponent(lblID)
                                .addComponent(lblName))
                            .addGap(45, 45, 45)
                            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                                .addComponent(txname)
                                .addGroup(layout.createSequentialGroup()
                                    .addComponent(txid, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                                    .addGap(0, 0, Short.MAX_VALUE))))))
                .addContainerGap(24, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                    .addComponent(lblID)
                    .addComponent(txid, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                    .addComponent(lblName)
                    .addComponent(txname, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                    .addComponent(lblDesc)
                    .addComponent(txdesc, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(btnsave)
                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        
        pack();
    }    
    
    private void btnsave_click(ActionEvent e) {
        if (ctrlMasterTrans.Add_Product(Integer.parseInt(txid.getText()), txname.getText(), txdesc.getText())) {
            JOptionPane.showMessageDialog(this, "Add Success");
            this.hide();
            frmController fc;
           
            Container obj = this.getParent();
           
            Class cls = obj.getClass();
            while(!cls.getName().contains("frmController")) {
               obj = obj.getParent();
               cls = obj.getClass();
            
            }
            fc = (frmController) obj;
            fc.ShowProductList();
            
            this.dispose();
        }
        
    }
    
    private JButton btnsave;
    private JLabel lblDesc;
    private JLabel lblID;
    private JLabel lblName;
    private JTextField txdesc;
    private JTextField txid;
    private JTextField txname;
}

This InternalFrm2 which is View shows blank JTextField to be inputted and saved as product. Click user event on ‘Save’ button will be handled by Controller. Basically, View shouldn’t access directly to Model Layer.

That’s it, try to run it.

Summary

Applying MVC should be having classes which have specific role either as Model or View or Controller. Controller is a bridge between Model & View Layers.
View shouldn’t access directly to Model and so vice versa. It has to flow through Controller.

The purpose to apply MVC are to make application loose coupling enough between every layers and easier to maintain. This is naturally true for large application.
Since simple or small application might not need MVC method then implementation of MVC must align with business requirements.

Regards,
Agung Gugiaji

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s