to apply MVC design concepts to the creation
of a simple Java Swing GUI
to illustrate NetBeans' GUI Designer mode in which much of the code is
created automatically through swing component forms
(which are in reality XML files interpreted by NetBeans).
The application we're creating is very simple. It should look something
like this:
Clicking the Next button displays in the textfield
a random sequence of numbers
between 0 and 99. The Reset button clears the textfield and resets
the random number generator.
MVC Construction in NetBeans Designer
Although NetBeans' Designer gives the option of creating a
so-called
Desktop Application, we do not want to use this since it takes us
too far afield from the basic Swing applications.
Instead, we'll always work from Java Applications and add the
the basic Swing containers (JFrame, JPanel, etc)
as NetBeans' visual forms.
The NetBeans Designer forms are, in effect, a combination of an XML
file and a Java class. The XML file is used by the designer to provide
a visual interface for constructing the component. The Java class is the
actual code in which parts are "automatically generated" according
to the XML file, and not directly editable.
View/Controller event handling
A Java event generation consists of an object which generates the event
(like a Button) and an object which performs an action when the generator
generates the event. The event generator is part of the View, belonging
directly to one of the visual components. NetBeans will easily lead you to
making the visual component be the handler, but this is exactly what
we want to avoid, in order to maintain the separation of the View and
Controller.
In our scheme, the Main program will be the controller and the
handler of all the events (with some exceptions) generated by objects in
the visual components. Permitting the Main program to defined an
event handler for a visual component means that the visual component
must define a public interface function which associates a listener defined
outside the component directly to the object which generates the event.
For example, suppose we have defined a
MyVisualComponent class which contains a button; this can all be
done in the Designer.
Then, however, we want to break out of the design mode
into source
mode and define an interface function,
addButtonActionListener, making the class look like this:
public class MyVisualComponent extends ... {
public void addButtonActionListener(ActionListener listener) {
button.addActionListener(listener);
}
public MyVisualComponent() {
// ...
}
private JButton button = new JButton("My Button");
}
The Main class, acting as controller, becomes the button's event handler by
virtue of defining:
public class Main ... {
private MyVisualComponent mvc = new MyVisualComponent():
public Main() {
mvc.addButtonActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) { ... }
}
);
}
}
Handler objects
The handler is an object defined by an anonymous inner class specified by
the code:
new ActionListener() {
public void actionPerformed(ActionEvent evt) { ... }
}
What happens is that Java translates this into code something like this:
class MyClass implements ActionListener() {
public void actionPerformed(ActionEvent evt) { ... }
}
ActionListener myobj = new MyClass();
mvc.addButtonActionListener(myobj);
In particular, the class MyClass is an inner class in that
it is defined within the larger class
within a function. Inner classes can also be defined at the data level,
but this is scoped differently.
The class MyClass is never given a name per se; Java references
these anonymous classes by number. The instantiation is also anonymous.
In certain cases it is advantageous to name the object, myobj, so that
it can be reusable, e.g.,
ActionListener myobj = new ActionListener() {
public void actionPerformed(ActionEvent evt) { ... }
};
mvc.addButtonActionListener(myobj);
Retrieving/Setting content in a visual component
If, say, the visual component has a textfield
the clicking of a button could signify that the controller
should read the content and do something with it. In order to maintain the
View/Controller separation, we again define interface function(s)
to handle the needs of the application. For example, consider
MyVisualComponent class holding a JTextField component myinfo.
We may have a setup like this:
public class MyVisualComponent extends ... {
public String getMyinfoText() {
return myinfo.getText();
}
public MyVisualComponent() {
// ...
}
private JTextField myinfo = new JTextField();
}
In the controller the code which reads the text of the textfield when the
button is clicked would look something like this:
public class Main ... {
private MyVisualComponent mvc = new MyVisualComponent():
public Main() {
mvc.addButtonActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
String infoText = mvc.getMyinfoText();
}
}
);
}
}
Interface functions
Observe the nascent systematic creation of the interface function names:
There is no official rule or other religious dogma we're following, it's
just a convention that we make up. Depending on the needs of the application,
or how reusable we wish to make our visual component, other interface
functions for myinfo may apply:
public void setMyinfoText(String text) { myinfo.setText(text); }
public void setMyinfoEnabled(boolean b) { myinfo.setEnabled(b); }
The construction
The construction asks you to add code in a number of places.
You can either key it in (recommended), or for expediency,
copy and paste it in. If you do copy and paste, you should
reformat the source code by simply right-clicking the editor
window and selecting Format.
Select File New Project
In the New Project window, select the Java category,
and choose Java Application. Click Next.
Choose the project name "MVCExample".
Keep Create Main Class and Set as Main Project checkboxes checked.
Click Finish.
we will let the created package mvexample serve
as the repository for our Controller classes.
Right-click on the project and select
New JFrame Form.
Enter this information (the views package
will be automatically created):
Class Name:
Frame
Package:
views
NetBeans present you with the JFrame in Design mode in which one can
use the accompanying Palette and other tools to create many GUI code
features automatically.
The Source mode is available for entering code at appropriate
places; it also contains generated code which is not editable.
Modify Frame in Design mode.
Right-click on the form and select
Set Layout FlowLayout
Right-click on the form and select
Properties. Set the title property to MVC Example.
Make the following additions to the form using the
Swing Controls from the Palette.
Select
and drag two Buttons and a Textfield onto
the JPanel form.
Right-click on jButton1:
change the Text to "Next",
change the variable name to "next".
For jButton2:
change the Text to "Reset",
change the variable name to "reset".
For jTextField1:
change the Text to "" (empty),
change the variable name to "output".
Again right-click on jTextField,
select Properties, and
set the columns property to 10.
uncheck the checkbox for the editable property.
select the choice box for the background property
and change the color to white (in the AWT Palette) or whatever you like
Edit mvcexample.Main.
Modify the class code to be this:
private final views.Frame frame = new views.Frame();
public Main() {
frame.setVisible(true);
}
public static void main(String[] args) {
new Main();
}
Save and test run the program. The visual appearance of
the application should be right although nothing happens yet.
Frame class
which can be used by the Main class (the controller) to determine
the semantic behavior of the visual object.
Towards this end, we need to manually
edit Frame (in source mode)
and add these desired capabilities.
Edit Frame in Source mode. Add this import
line (just underneath the package statement):
import java.awt.event.*;
Add these member functions
(just inside the class Frame declaration):
public void setOutputText(String text) {
output.setText(text);
}
public void addNextActionListener(ActionListener a) {
next.addActionListener(a);
}
public void addResetActionListener(ActionListener a) {
reset.addActionListener(a);
}
To complete the application we create the Model and join the Model to
the View inside the Controller, i.e. the mvcexample.Main class.
Create the Java ClassData in the models package.
Add these into the models.Data class:
private java.util.Random rnd = new java.util.Random();
public Data() {
reset();
}
public int next() {
return rnd.nextInt(100);
}
public void reset() {
rnd.setSeed(0);
}
Edit mvcexample.Main. Add the import line:
import java.awt.event.*;
Add this data declaration after class Main
(same idea as the frame variable):
private final models.Data data = new models.Data();
Add this code inside the Main
constructor after the "frame.setVisible(true);"
statement:
frame.addNextActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
frame.setOutputText("" + data.next());
}
}
);
frame.addResetActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
frame.setOutputText("");
data.reset();
}
}
);
Finally, run the program and test it.
Create a standalone application
Select Build Build Main Project. This creates a JAR
file with all the class files. It is held in the dist folder of the
project directory MVCExample. As suggested in the output, to run it,
change directory to this folder and do:
java -jar MVCExample.jar
In Linux using the GNOME Desktop, you can create a Launcher which basically
has the above statement, something like:
java -jar PATH-TO-THE-JAR-FILE/MVCExample.jar
In Windows
if you want to distribute this application to Desktop users, have them download and
store MVCExample.jar in some dedicated folder, which,
for simplicity we'll say is the C:\ folder. Then:
Right-click on the desktop and select
New Shortcut. This brings up wizard
which asks for a location.
You should be able to simply type javaw.exe
(not java.exe). These Java executables are most
likely already a user's Windows system in an
executable-accessible folder (like \windows\system32).
Click Next, then Finish.
Rename the shortcut whatever you like
and then right-click on it to obtain the properties.
Change Start in to "C:\"
In the Target entry append this:
-jar MVCExample.jar
Click OK and then try out the application shortcut by
double-clicking it.