GUIs

View/Controller Separation

A GUI (Graphical User Interface) application consists of at least two distinct components: It is important to keep these functions separate, putting them into different classes where the classes reside in different packages. The code which creates the view presentation is called Java Swing with classes and interfaces mostly within the packages
javax.swing
java.awt
The basis of most Java Swing GUI applications is a single object created from an instance of a javax.swing.JFrame class extension. Another common auxiliary view class is a javax.swing.JDialog extension. Both JFrame and JDialog objects are swing Containers in that they hold non-container objects, such as JButton and others, which are responsible for capturing and presenting data and generating events. As indicated in our discussion, we do not want these graphical components to be the handlers of the events.

NetBeans Designer

The NetBeans Designer presents the visual components as forms which are, in effect, combinations of an XML file (SomeClass.form) and a Java source file (SomeClass.java). The XML file specifies the visual interface used for constructing the component. The two common Swing forms are JFrame Form and JDialog Form. The class contains portions of code which are "designer generated" and not directly editable.

In Design mode, components can be configured easily via point and click functionality. It is important to keep the Navigator window active, as it reveals a hierarchy of window components used and allows access to components not easily accessed otherwise. NetBeans provides a Source mode available for editing the class so that we can add our own interface methods used by the non-graphical Controller class.

The designer relieves us from the details of programming the add operations, providing a simple drag-and-drop, point-and-click adding of components, in particular through Palette menus, The Properties, Set Text and Change Variable Name features provides all the relevant component settings and allows the user to easily set them.

Limitations of Designer

NetBeans Designer gives the option of creating a so-called Desktop Application, but we do not want to use this since it takes us too far afield from the basic Swing applications. We will always work from Java Applications and add the basic Swing containers (JFrame, JPanel, etc) as NetBeans' visual forms.

The Designer does not really support our style of view/controller code separation. For example, the JFrame Form automatically adds a main function, but this is not where we want the application main function to be located.

More significantly, the Designer allows you to specify event handlers for a components using the Events sub-menu gotten from the right-click menu on the component. There are two "lures" to add handler code say, for a button: Please avoid this approach, because both add the event handler code inside the view class.

The HelloGUI application

This is a very simple application which illustrates the desired controller/view code separation. Please make sure the NetBeans Navigator (Window ⇾ Navigator) window is visible as this window will frequently provide the easiest way to access layout objects as well as other GUI components.

Create the initial structure

  1. Select File ⇾ New Project
  2. In the New Project window, select the Java category, and choose Java Application. Click Next.
  3. In the New Java Application window, set this and click Finish.
    Project Name: HelloGUI
  4. The auto-generated hellogui package can serve as our controller package. But we want a different Main class, so delete:
    hellogui/HelloGUI.java
    and, within the package hellogui, create a new Main class with the name Controller, getting:
    hellogui/Controller.java
    You can right-click on the package itself and create the class.
  5. Now we want to create our views class. First we'll create the views package. Right-click on the project, or "Source Packages" and select
    New ⇾ Java Package Give it the name views. Right-click on the views package and select New ⇾ JFrame Form. The first time you do this on a system, you will have to locate this choice through the "Other" selection, thereafter, it will appear directly:
    New ( ⇾ Other ⇾ Swing GUI Forms ) ⇾ JFrame Form
    The goal is to generate this class within the views package:
    MyFrame
    
    Alternatively, you can create the You can create the views package and MyFrame class both in one step when you create the JFrame Form by specifying the class in the dialog:

    Class Name: MyFrame
    ...
    Package: views
  6. (This step is optional) Delete the main function in views.MyFrame. Be carefult to delete only the main function and nothing else! Go into Source mode, locate and delete it.
    We never will use the auto-generated main function in a view class.
  7. Edit the class Controller.java. Modify the code to be what is indicated. The easiest thing to do is to select-copy and paste it over top of the current Controller class code.

    hellogui.Controller
    package hellogui;
     
    import views.MyFrame;
     
    public class Controller {
     
      private final MyFrame frame = new MyFrame();
     
      public Controller() {
        frame.setTitle( getClass().getSimpleName() );
        frame.setLocationRelativeTo(null);
     
        // event handlers
      }
     
      public static void main(String[] args) {
        Controller app = new Controller();
        app.frame.setVisible(true);
      }
    }
    select
    You can generate this code from the dedicated course site: Enter the values:
    controller package: hellogui
    controller class:   Controller
    frame package:      views (the default)
    frame class:        MyFrame
    
Run the program. You can use the Green Run button and select hellogui.Controller as the target Main class. We can follow the progress:

The initial layout of the JFrame created by Designer is called Free Design. It is meant to be an easy drag-and-drop layout which positions components at the desired (x,y) coordinate without further modifications. Although this is not a good general-purpose layout, it is good enough for now.

Add a button

There are 3 distinct parts to this or any such operation:
  1. Add and configure the component in Design mode in the frame.
  2. Add the component interface access method in Source mode in the frame.
  3. Add the event handler operations in the controller using the interface access method.
Before we proceed, we'll restate our admonishment above:

Avoid Designer's built-in event handling Don't try to set up event handlers in NetBeans Designer, because they'll be implemented in the frame, not the controller.

A.   First, the button. Edit views/MyFrame.java. Initially it is presented in Design mode.
  1. Right-click on the form and select Set Layout. Observe the Free Design choice bolded, indicating that this choice is the layout used by NetBeans in contrast to the default Border Layout.. For simplicity we will keep this layout.
  2. Add a JButton into the frame. You can do this in one of three ways:
    1. Locate Button in the Swing Controls section of the Palette window. Drag and drop it onto the frame.
    2. Right-click on the JFrame, select Add From Palette ⇾ Swing Controls ⇾ Button.
    3. Locate the JFrame in the Navigator window. Right-click on it and make the choices as in the previous item.
    For now, the button can repositioned be anywhere.
  3. Right-click on the button. Select Edit Text and set the text to Hello.
  4. Right-click on the button. Select Change Variable Name... and set the new name to hello.
B.   Next the access method. Change to Source mode for views.MyFrame. Add the access method, as depicted, just below the class declaration:

MyFrame.java
package views;
 
public class MyFrame extends javax.swing.JFrame {
 
  public JButton getHelloButton() {
    return hello;
  }
 
  public MyFrame() {
    initComponents();
  }
  ...
You'll see that the declaration line is flagged as an error because the definition for JButton is missing. Fix this by:
Right-click on the document and choose Fix Imports from the popup menu, or select it from the Source menu.
The outcome should be to add the import line:
import javax.swing.JButton;
C.   Finally, the event handler in Controller.java. We'll show a useful technique for completing the code.
  1. First, key in the following code in the indicated "event handlers" section:

    Controller.java
    package hellogui;
      ...
      public Controller() {
        ...    
        // event handlers
     
        JButton hello = frame.getHelloButton();
     
        hello.addActionListener(new ActionListener());
      }
      ...
    }
  2. Then fix the imports as we did in the previous step, adding the import lines:
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
  3. Now fix the missing methods. Click on the red indicator replacing the line number. There is a single choice which you should choose:
    Implement all abstract methods
    
  4. Observe the outcome of the generated code:

    Controller.java
    ...
    // event handlers
    hello.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent ae) {
        throw new UnsupportedOperationException("Not supported yet."); 
      }
    });
  5. Replace the body of actionPerformed by
    System.out.println("Hello World");
Test it by running the application and pressing the button, looking for the response in the output.

Add a TextField

The textfield we'll add will interact with the button in a simple way. The idea is to enter text into the textfield, press the hello button and have the text be converted to upper case. Here are the steps:
  1. Access the frame in Design mode. Add a JTextField object into the frame. NetBeans Designer lists it as "Text Field." Drag it to wherever you like.
  2. Right-click on the textfield. Select Edit Text and erase the initial text, resizing the textfield. Again, select Change Variable Name ... and set the new variable name to
    sampleTextField
  3. Edit the frame in Source mode. Add this access methods:

    MyFrame.java
    ...
    public class MyFrame extends javax.swing.JFrame {
      ...
      public JTextField getSampleTextField() {
        return sampleTextField;
      }
     
      public TheFrame() {
        initComponents();
      }
      ...
  4. As before, choose Fix Imports from the right-click menu on the document to add the necessary import.
  5. Next, modify the controller code for the existing button event handler:

    Controller.java
    package hellogui;
      ...
      public Controller() {
        ...    
        // event handlers    
     
        JButton hello = frame.getHelloButton();
        JTextField sampleTextField = frame.getSampleTextField();
     
        hello.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            String sample_text = sampleTextField.getText();
            sampleTextField.setText(sample_text.toUpperCase());
          }
        });
      }
      ...
    }
    As usual, run Fix Imports.
Test it by running the application.

Java Swing text components (TextFields, TextAreas and Labels) have the text property in the sense that they use the standard public getter/setter accessor methods:
public String getText();
public void setText(String newText);

Add a TextArea

The textarea we'll add will interact with the button and the previous textfield. The variation will be this: Make these modifications:
  1. Access the frame in Design mode. Add a JTextArea object into the frame. NetBeans Designer lists it as "Text Area." Drag it onto the form and organize the form to look something like this:
  2. Right-click on the textarea. Select Change Variable Name... and set the new variable name to
    sampleTextArea
  3. Edit the frame in Source mode. Add this access methods followed by the usual Fix Imports:

    MyFrame.java
    ...
    public class MyFrame extends javax.swing.JFrame {
      ...
      public JTextArea getSampleTextArea() {
        return sampleTextArea;
      }
     
      public MyFrame() {
        initComponents();
      }
      ...
  4. Make the textarea un-editable by the user. This can be done directly by editing the Properties of the textarea, unchecking the editable property, or it can be done directly in the controller code by adding this line into the constructor:

    Controller.java
    package hellogui;
      ...
      public Controller() {
        ...    
        frame.getSampleTextArea().setEditable(false);
     
        // event handlers
        ...
      }
      ...
    }
  5. Finally, modify the code of the existing button event handler as indicated, running Fix Imports as usual.

    Controller.java
    package hellogui;
      ...
      // private int count = 0;  // will work
     
      public Controller() {
         ...    
        // event handlers    
     
        JButton hello = frame.getHelloButton();
        JTextField sampleTextField = frame.getSampleTextField();
        JTextArea sampleTextArea = frame.getSampleTextArea();
     
        // int count = 0; // won't work
     
        hello.addActionListener(new ActionListener() {
          private int count = 0;
          @Override
          public void actionPerformed(ActionEvent e) {
            String sample_text = sampleTextField.getText();
            sampleTextField.setText("");
     
            String new_line 
                = String.format("%s: %s\n", ++count, sample_text.toUpperCase());
     
            String area_text = sampleTextArea.getText();
            sampleTextArea.setText(area_text + new_line);
          }
        });
      }
      ...
    }
Test it by running the application.

A point to bring out is that the newly added count data member is considered to be part of the application's state, like the frame member.

Local variables used in actionPerformed

If you try using count as a local variable instead of a member variable, you'll get an error. Try commenting out
// private int count = 0;
and uncommenting the subsequent line:
int count = 0;
The error that comes up is this:
local variable referenced from an inner class must be final or effectively final
Trying to explicitly make it final won't work because of the "++count" usage. In contrast, the other local variables hello, sampleTextField and sampleTextArea, even though not declared final are "effectively final" since they are not altered after being declared.

The status of count is like that of frame; they are both "state variables" of the application.

Make a Standalone Application

At this point, HelloGUI, can be a standalone application since all input and output are from the GUI application itself. Check it for yourself, try running
Run ⇾ Clean and Build Project
or use the "Broom and Hammer" icon. It generates the file HelloGUI.jar in the dist folder of the project (viewable through the Files window). To get this JAR file to your desktop, you have to do so through the file system explorer; but once you have done so, you can run it by double-clicking on Windows or MAC.

Possibilities for the handler object

The event handler is an anonymous object instantiated from an anonymous class which implements the ActionListener interface in the code used above:
public class Controller {
  ...
  public Controller() {
    ...
    hello.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent ae) {
        ...
      }
    });
  }
});
All of these concepts be discussed later of the course.

NetBeans, and one might say Java as well, recognizes and prefers this style of event handler object. Here are some alternatives:
  1. The handler as an instance of a private inner class, ButtonHandler:
    package hellogui;
     
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JTextArea;
    import javax.swing.JTextField;
    import views.MyFrame;
     
    public class Controller {
     
      private final MyFrame frame = new MyFrame();
     
      private class ButtonHandler implements ActionListener {
        private JTextField sampleTextField = frame.getSampleTextField();
        private JTextArea sampleTextArea = frame.getSampleTextArea();
        private int count = 0;
     
        @Override
        public void actionPerformed(ActionEvent ae) {
     
          String sample_text = sampleTextField.getText();
          sampleTextField.setText("");
     
          String new_line
              = String.format("%s: %s\n", ++count, sample_text.toUpperCase());
     
          String area_text = sampleTextArea.getText();
          sampleTextArea.setText(area_text + new_line);
        }
      }
     
      public Controller() {
        frame.setTitle(getClass().getSimpleName());
        frame.setLocationRelativeTo(null);
     
        JButton hello = frame.getHelloButton();
     
        hello.addActionListener(new ButtonHandler());
      }
     
      public static void main(String[] args) {
        Controller app = new Controller();
        app.frame.setVisible(true);
      }
    }
    There is definitely no point in making ButtonHandler be a public class in an external file.
  2. The handler as an instance of a local inner class.
    package hellogui;
     
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JTextArea;
    import javax.swing.JTextField;
    import views.MyFrame;
     
    public class Controller {
     
      private final MyFrame frame = new MyFrame();
     
      public Controller() {
        frame.setTitle(getClass().getSimpleName());
        frame.setLocationRelativeTo(null);
     
        class ButtonHandler implements ActionListener {
          private JTextField sampleTextField = frame.getSampleTextField();
          private JTextArea sampleTextArea = frame.getSampleTextArea();
          private int count = 0;
     
          @Override
          public void actionPerformed(ActionEvent ae) {
     
            String sample_text = sampleTextField.getText();
            sampleTextField.setText("");
     
            String new_line
                = String.format("%s: %s\n", ++count, sample_text.toUpperCase());
     
            String area_text = sampleTextArea.getText();
            sampleTextArea.setText(area_text + new_line);
          }
        }
     
        JButton hello = frame.getHelloButton();
     
        hello.addActionListener(new ButtonHandler());
      }
     
      public static void main(String[] args) {
        Controller app = new Controller();
        app.frame.setVisible(true);
      }
    }
These are overkill. As an inner or local class, ButtonHandler only ever gets used once. In the last case, the variable handler only gets used once. Why do we need it these names? We don't. An unnamed object of an anonymous class provides the solution 99% of the time and it saves us from having to invent names when there is really no need for doing so.

Java 8 Lambda Expressions

Java 8 has so called "lambda expressions" which can serve as event handlers. These lambda expressesions can replace the anonymous inner classes that we are using. You'll see that NetBeans asks you whether you want to replace what we have by a lambda expression equivalent. It is not the purview (currently) of this class to explain lambda expressions.


© Robert M. Kline