Skip to content

Tutorial

Michael Karneim edited this page May 9, 2018 · 14 revisions

With this tutorial we want to give you a detailed introduction to programming with the Beanfabrics framework.

Goals

You will learn

  • how to divide the application into several layers
  • how to implement a simple PM
  • how to create a GUI
  • how to bind view controls to PM components
  • how to assemble the application

Requirements

To use the Beanfabrics framework you should have a good knowledge of Java programming and basic knowledge of the Java Desktop technology (Swing). It is also helpful being familiar with the MVC pattern. To compile and run programs you need Java 7 (or a higher version) and the Beanfabrics library in the classpath. Additionally, it will help (but is not a requirement) that you use an integrated development environment (IDE) like Eclipse (or NetBeans) with a GUI-Designer.

Topic: The Directory Viewer

In this tutorial we will create a Directory Viewer component. This is a component that lists the contents of a directory.

This screenshot shows the final component how it looks like on Windows.

---!!!--- TODO include DirectoryViewer.png ---!!!---

This simple user interface consists of a textfield and a table component.

  • The textfield shows the path to a directory.
  • The table shows the list of files contained in that directory, each row with icon, filename and size. By clicking on a table header you can sort the rows by the contents of that column.

Implementation

The application architecture

According to the Beanfabrics layered architecture we arrange the classes of our sample application as illustrated by the following object diagram:

---!!!--- TODO include Quickstart-layers.png ---!!!---

This diagram shows all major classes of the sample application with their relationships, distributed over three separate layers. A line represents an object reference from one class (thick end) to another (thin end). A pen symbol indicates that the undelying class is custom made whereas all other classes belong to the standard Java library or the Beanfabrics library.

In the following three sections I will describe the tiers and their classes in detail.

The application model

The application model is the lowermost layer. It normally resides inside the application tier and contains classes that provide application services and data.

Our sample application - the Directory Viewer - needs to show information about the files in a directory. This includes filename, file icon and size. The application model can look like this:

---!!!--- TODO include Application-Tier.png ---!!!---

The box on the left side represents the directory. The box on the right side represents all files contained in this directory.

To list its files we can use java.io.File.listFiles(). To read a file's name and size we also use methods of java.io.File. Reading a file's icon requires some more code, so we have created a small utility class called Quickstart_FileIconUtil that hides these implementation details. Internally it delegates calls to a javax.swing.JFileChooser object.

Now we know where the data comes from. Let's proceed to the next layer that uses this data and that prepares it for the GUI.

The presentation model

The presentation model (PM) is a composition of presentation model components. It translates parts of the application model to its presentational form and handles user input events by invoking methods on the application model or other services.

As mentioned in the overview a PM component is a JavaBean that encapsules a specific detail of the application state. This includes some value, for example a simple text, as well as a validation state and - depending on the type - other typical attributes like "editable" and "mandatory". It provides methods for accessing, converting, formatting, and validating the content. It informs listeners about state changes and is implementing the PresentationModel interface.

In this section we create the PM for the DirectoryViewer.

Let's have a look on the following diagram:

---!!!--- TODO include Presentation Model Tier.png ---!!!---

This is the structure of the PM of our sample application. The topmost box is a DirectoryViewerPM. This is the root PM component of the application. It directly or indirectly references all other PM components.

Here is a short description about these PM components:

  • Quickstart_DirectoryViewerPM: this is a custom PM component for our directory viewer. It reads a directory and fills the data into a TextPM (directory path) and a ListPM (list of files).
  • ListPM: this is a standard PM component for a list of other PM objects. In this case we use it as a container for FilePM objects.
  • Quickstart_FilePM: this is a custom PM component for a file (java.io.File). It reads the file attributes "name", "length", and "icon" and initializes the values of its child nodes.
  • TextPM: a standard PM component for a text (java.lang.String)
  • IntegerPM: a standard PM component for an integer number
  • IconPM: a standard PM component for an icon (javax.swing.Icon)

Besides these components the Beanfabrics library offers a set of prefabricated model components for general purpose that easily can be extended and combined for special purpose.

Let's go into detail and start with the FilePM.

FilePM

The Quickstart_FilePM is a custom PM component representing a File. In our application it is used as the model for a table row (see screenshot above). It has the properties "filename", "fileicon" and "filesize".

Creating this class and adding properties is as easy as this:

public class FilePM extends AbstractPM {
    TextPM filename = new TextPM();                
    IconPM fileicon = new IconPM();                
    IntegerPM filesize = new IntegerPM();        

    public FilePM() {                            
        PMManager.setup(this);                
    }
}

What are we doing here?

  • We extend the class AbstractPM. This is the base class of all PM components in Beanfabrics.
  • For each of the three relevant file attributes (filename, fileicon, filesize) we create a member field of an appropriate type.
  • The call of PMManager.setup initializes this PM.

In this way we have defined the structure of the PM.

With the next step we configure the behavior of the child nodes.

Because we don't want the model to be editable within the GUI we add the following code to the constructor:

filename.setEditable(false);                    
fileicon.setEditable(false);                    
filesize.setEditable(false);                    

The method setEditable( boolean) defines if a model is editable within the GUI. View components have to comply with this attribute and prevent the user from entering data.

Additionally we want to format the filesize value accoring to the format "# Byte". We define the format by calling IntegerPM.setFormat(...).

filesize.setFormat( new IntegerPM.Format(new DecimalFormat("# Byte")));        

Finally we add a method to set a File object and to fill its properties with the appropriate values.

public void setFile( File f) {                                        
    // set filename
    filename.setText(f.getName());                                        
    // set file icon
    fileicon.setIcon( FileIconUtil.get().getIcon(f));                   
    // set file size
    if ( f.isDirectory()) {                                            
        filesize.setText("");                                            
    } else {                                                        
        filesize.setLong(f.length());                                    
    }                                                                
}                                                                    

This makes our FilePM complete. We now can use it as a PM for a single table row.

Let's now proceed to the root PM of the application.

DirectoryViewerPM

The Quickstart_DirectoryViewerPM is the root node of this application's PM. It is the PM for the Quickstart_DirectoryViewerPanel which I will describe later in detail. Just note now that that panel contains the textfield showing the directory path and the table with the list of files. When we create a DirectoryViewerPM we must provide some PM components that can be bound to these GUI controls.

Let's see how we design the PM's structure accordingly:

public class DirectoryViewerPM extends AbstractPM {

    TextPM directoryPath = new TextPM();
    ListPM<FilePM> entries = new ListPM<FilePM>();

    public DirectoryViewerPM() {                
        PMManager.setup(this);                
    }                                             

    public DirectoryViewerPM(File dir) {     
        this();                                    
        setDirectory(dir);                        
    }                                            
}  

What are we doing here?

  • Again we extend AbstractPM as we have done with FilePM.
  • We create two properties:
    • the field directoryPath is a standard TextPM that holds the directory path and later will be connected to the textfield in the GUI.
    • the field entries is a standard IListPM which is a list-like container for PM objects of the specified type. It provides the usual methods for adding, removing and getting elements. By implementing the IListPM interface it can be used as a PM for the BnTable control. The generic type parameter is set to FilePM which defines the list's element type.
  • We create two constructors:
    • a default constructor that calls the PMManager to setup this model.
    • a second constructor with a file parameter that points to the directory to read. It's more convenient to create and populate the PM with a single statement.

The job of setDirectory( File ) is to read the directory's name and children, and to populate the model, that means to create child nodes for every file and fill them with the relevant values. Let's see how this is done:

public void setDirectory(File dir) {                     
    // set directory path                                  
    directoryPath.setText(dir.getAbsolutePath());        
    // load directory list                                  
    File[] files = dir.listFiles();                        
    // remove old entries (if any)                          
    entries.clear();                                    
    // add new entries                                         
    for( File file: files) {                            
        FilePM newEntry = new FilePM();                
        newEntry.setFile(file);                            
        entries.add(newEntry);                            
    }                                                    
}                                                         

What are we doing here?

  • We assign the directory's file path to the "directoryPath"'s text attribute.
  • We read the directory's file list
  • and for each file
    • we create an FilePM model object
    • set the file into the model
    • and add it as an element to "entries"

With that we know the complete application's presentation model.

For creating the visual part of the GUI let's proceed to the topmost layer. There we will see how we can bind view components to PM components.

The presentation view

The presentation view is the other layer (besides the presentation model) inside the presentation tier and is the topmost layer of this application. As the name suggests it essentially contains presentation view components.

The following diagram shows the components we use in our sample application:

---!!!--- TODO include View Tier.png ---!!!---

Let's see what they are doing:

Let's go into detail and start with the DirectoryViewerPanel.

DirectoryViewerPanel

As mentioned before, the Quickstart_DirectoryViewerPanel is a view on the Quickstart_DirectoryViewerPM. Its job is to show the state of the assigned DirectoryViewerPM on the GUI.

To achive that we have to implement the generic View interface with the appropriate generic type argument.

Let's see how we can do that:

public class DirectoryViewerPanel extends JPanel implements View<DirectoryViewerPM> {

The View interface declares a generic type parameter M that must be set to a concrete model class. That means that an instance of this panel can be bound to a PM of that given type.

This interface requires us to implement a setter and a getter method for the model object. We can do this easily by delegating the method call to a ModelProvider object:

ModelProvider localProvider = new ModelProvider();  

/** {@inheritDoc} */                                    
public void setPresentationModel(DirectoryViewerPM pModel) {     
    // set the model...                                   
    localProvider.setPresentationModel(pModel);                     
}                                                        

/** {@inheritDoc} */                                    
public DirectoryViewerPM getPresentationModel() {                
    return localProvider.getPresentationModel();                    
}                                                        

The ModelProvider is a convenient little helper for us. It is a container for a PresentationModel object. Basically its job is to inform about changes on the model and its decendent nodes. To get informed about changes on a specific node you can register a ModelProviderListener. It will listen for changes on each node of the specified Path. This listener will be informed whenever any of these nodes is changing.

Now we can create two specialized view components, a textfield and a table, that render two different parts of the PM:

// the textfield for the directory path                      
BnTextField tfLocation = new BnTextField();            
// the table for the file listing                      
BnTable tblEntries = new BnTable();                    

public DirectoryViewerPanel() {                            
    // bind the view components to the model provider:      
    // first the textfield ...                              
    tfLocation.setModelProvider(localProvider);            
    tfLocation.setPath(new Path("this.directoryPath"));    

    // ... and then the table                              
    tblEntries.setModelProvider(localProvider);            
    tblEntries.setPath(new Path("this.entries"));        

To register a view component for changes on a PM we set the attributes "modelProvider" and "path". The textfield tfLocation is now bound to the TextPM at the path "this.directoryPath". And the table tblEntries is bound to the ListPM at the path "this.entries", which contains a list of FilePM objects.

Additionally we have to configure the table columns by setting an array of BnColumn objects. For each column we define the relative path from a FilePM to the node with the required value as well as the column header, and optionally the initial column width.

// configure the table how to map the columns                                        
tblEntries.setColumns(                                                              
        new BnColumn[] {                                                          
                new BnColumn(new Path("this.fileicon"), " ", 25)                
                , new BnColumn(new Path("this.filename"), "File")                
                , new BnColumn(new Path("this.filesize"), "Size")                
                }                                                                
        );                                                                        

What are we doing here?

  • We confgure the table to show three columns:
    • The first column shows the file icon and has an empty (" ") header. The initial column width is 25 pixel.
    • The second column shows the filename. The header is "File".
    • The last column shows the file size. The header is "Size".

The following code does the graphical component layout:

// configure the table look                      
tblEntries.setIntercellSpacing(new Dimension(0,0));
tblEntries.setGridColor(Color.white);

// then do the layout ...

setLayout( new BorderLayout());

JPanel headerPanel = new JPanel();
headerPanel.setLayout( new BorderLayout());
headerPanel.add(tfLocation, BorderLayout.CENTER);
add(headerPanel, BorderLayout.NORTH);

JPanel centerPanel = new JPanel();
centerPanel.setLayout( new BorderLayout());
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport().add(tblEntries);
scrollPane.getViewport().setBackground(Color.white);
centerPanel.add(scrollPane, BorderLayout.CENTER);
centerPanel.setBorder( new EmptyBorder(8,8,8,8));
add(centerPanel, BorderLayout.CENTER);

So, what is left to do?

The application

To finish the application we solely need a class that combines the components from the different layers and some main method.

DirectoryViewer

The Quickstart_DirectoryViewer combines a Quickstart_DirectoryViewerPanel, a Quickstart_DirectoryViewerPM and a File.

Let's have a look at the following diagram:

---!!!--- TODO include DirectoryViewer-Component.png ---!!!---

The DirectoryViewer has references to a view (DirectoryViewerPanel), a presentation model (_ DirectoryViewerPanel_) and some data (File). It assigns the data to the model, and the model to the view.

Look how this is done in code:

public class DirectoryViewer {                                         

    // Create the model                                                 
    DirectoryViewerPM model = new DirectoryViewerPM();        
    // Create the view                                                  
    DirectoryViewerPanel view = new DirectoryViewerPanel();            
    // Create a window for the view                                      
    JFrame frame = new JFrame();                                    

    public DirectoryViewer() {                                        

        // bind the view directly to the model                         
        view.setPresentationModel(model);                                        

        // place the view inside the window                              
        frame.add( view, BorderLayout.CENTER);                        

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        
        frame.pack();                                                
        frame.setLocationRelativeTo( null);                            
    }                                                                

    public void show(File dir) {                                    
        // put the data into the model                                  
        model.setDirectory(dir);                                    
        // show the window                                              
        frame.setVisible(true);                                        

    }                                                                
}

And this is how we can start the application:

public static void main(String args[]) throws Exception {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    // Create the application and show the data from the user's home directory    
    new DirectoryViewer().show(new File(System.getProperty("user.home")));

}

All classes together

Here is a list of all self-made classes of this sample application:

See also

  • For further information about the Beanfabrics framework please read the overview.
  • For sample code please see the examples .
Clone this wiki locally