A pattern for developing flexible classes for graphics
applications involves 3 parts:
A data representation of information to be
displayed.
-
The frame, canvas, buttons, etc. - components that make up the
display or view of the data.
-
Event handlers for the view components - controllers.
These 3 parts are often referred to as the
model-view-controller pattern.
The RectangleComponent object contains the data - the model.
1
2 public class RectangleComponent extends JComponent
3 {
4 private static final int BOX_X = 100;
5 private static final int BOX_Y = 100;
6 private static final int BOX_WIDTH = 20;
7 private static final int BOX_HEIGHT = 30;
8
9 private Rectangle box;
10
11 public RectangleComponent()
12 {
13 // The rectangle that the paintComponent method draws
14 box = new Rectangle(BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT);
15 }
16
17 public void paintComponent(Graphics g)
18 {
19 Graphics2D g2 = (Graphics2D) g;
20
21 g2.draw(box);
22 }
23
24 /**
25 Moves the rectangle to the given location.
26 @param x the x-position of the new location
27 @param y the y-position of the new location
28 */
29 public void moveTo(int x, int y)
30 {
31 box.setLocation(x, y);
32 repaint();
33 }
34 }
-
The RectangleComponent model stores a Rectangle object.
-
There is a mutator to modify the Rectangle.
-
The RectangleComponent also provides the aspects of the
view part. That is, the RectangleComponent is not
completely separated from the view.
The RectangleComponent inherits the ability to display
the rectangle from the JComponent class.
However, an application must still create the view by adding
this RectangleComponent to a frame and making the frame visible.
1
2 public class RectangleComponentViewer
3 {
4 private static final int FRAME_WIDTH = 300;
5 private static final int FRAME_HEIGHT = 400;
6
7 public static void main(String[] args)
8 {
9 final RectangleComponent component = new RectangleComponent();
10
... ...
31
32 JFrame frame = new JFrame();
33 frame.add(component);
34
35 frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
36 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
37 frame.setVisible(true);
38 }
39 }
The RectangleComponentViewer
-
creates a JFrame (line 32)
-
adds the model (a RectangleComponent) to the frame (lines 9
and 33)
-
sets the frames dimensions, etc. (line 35, 36)
-
makes the frame visible (line 37)
This view class does rely on the components it adds to the view
to be able to display themselves.
The paintComponent of each component added to a JFrame executes
whenever the JFrame is made visible.
1
2 public class RectangleComponentViewer
3 {
4 private static final int FRAME_WIDTH = 300;
5 private static final int FRAME_HEIGHT = 400;
6
7 public static void main(String[] args)
8 {
9 final RectangleComponent component = new RectangleComponent();
10
11 // Add mouse press listener
12
13 class MousePressListener implements MouseListener
14 {
15 public void mousePressed(MouseEvent event)
16 {
17 int x = event.getX();
18 int y = event.getY();
19 component.moveTo(x, y);
20 }
21
22 // Do-nothing methods
23 public void mouseReleased(MouseEvent event) {}
24 public void mouseClicked(MouseEvent event) {}
25 public void mouseEntered(MouseEvent event) {}
26 public void mouseExited(MouseEvent event) {}
27 }
28
29 MouseListener listener = new MousePressListener();
30 component.addMouseListener(listener);
31
32 JFrame frame = new JFrame();
33 frame.add(component);
...
38 }
39 }
The controller manages the user interaction with the view.
If the view consist of multiple components, the
controller may also consist of individual controllers - one
for each component.
Controller code executes in response to events, e.g. user
interaction with the view such as clicking on a canvas or a
button, typing in a textbox component.
Controller code to handle an event is called an event
handler.
Java controller's are typically created as instances of classes
called listeners.
A JComponent maintains a list of EventListeners. So creating
components that inherit from JComponent means that we can easily
add controllers (i.e. listeners) to such components using the
inherited methods for adding listeners.
Lines 13 - 27 of the RectangleComponentViewer class create a
listener to control mouse press events on the
RectangleComponent.
Lines 29 - 30 create a listener of this class and add this
listener to the RectangleComponent.
So the model (RectangleComponent) is primarily the data,
but through inheritance from JComponent it can be connected to
views and controllers:
- a JComponent subclass can override paintComponent to be able to draw itself
when asked to do so by a view (e.g., a JFrame with possibly
other components).
- a JComponent subclass inherits a list to hold event
handlers. So event handlers to control the component can be
added.
Each method gets a parameter of type MouseEvent passed to it
automatically when the corresponding event occurs.
The MouseEvent object has both public methods and some public
data members. The x and y coordinates of the mouse position are public data members.
11 // Add mouse press listener
12
13 class MousePressListener implements MouseListener
14 {
15 public void mousePressed(MouseEvent event)
16 {
17 int x = event.getX();
18 int y = event.getY();
19 component.moveTo(x, y);
20 }
21
22 // Do-nothing methods
23 public void mouseReleased(MouseEvent event) {}
24 public void mouseClicked(MouseEvent event) {}
25 public void mouseEntered(MouseEvent event) {}
26 public void mouseExited(MouseEvent event) {}
27 }
Only the mousePressed event is of interest, but all 5
methods of the MouseListener interface must be implemented. So
empty implementations are given for the other 4.
The MousePressListener class is used to provide a controller
for the RectangleComponent. So it needs a way of referencing the
component. See line 19.
The mousePressed method can reference component in
this case because:
- MousePressedListener is an inner class defined in method
main
- component is a final local variable also defined
in main.
Note: Inner classes defined in a method can only access local
variables of the method if they are declared with the
final qualifier!
So component is defined in main:
...
7 public static void main(String[] args)
8 {
9 final RectangleComponent component = new RectangleComponent();
...
The MouseListener interface has 5 methods to implement.
For the mouse controller used in this program, only one of the
methods does anything - mousePressed.
Instead of writing the 4 other methods with empty bodies, there
is a class that already does that for all 5 methods -
MouseAdapter.
So our mouse controller class can just inherit from
MouseAdapter and then override the 1 method
mousePressed.
Here is the revised MousePressedListener class using
inheritance from the MouseAdapter class:
class MousePressListener extends MouseAdapter
{
public void mousePressed(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
component.moveTo(x, y);
}
// Other methods in the MouseListener class are inherited
// from MouseAdapter (they do nothing)
}
If only one instance of a class is to be created and that
instance is just passed as a parameter to another method, it seems
extravagant to create a named class.
Java allows creating anonymous classes (unnamed classes)
from interfaces or from parent classes.
Here is how we could create an object that implements the
MouseListener interface without creating a named
class:
MouseListener listener =
new MouseListener()
{
public void mousePressed(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
component.moveTo(x, y);
}
// Do-nothing methods
public void mouseReleased(MouseEvent event) {}
public void mouseClicked(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
});
component.addMouseListener(listener);
Alternatively, we could replace the interface name
MouseListener above by the
class name MouseAdapter and omit the
Do-nothing methods.
We can even omit the listener variable and insert the
code that uses the anonymous class directly as the parameter:
component.addMouseListener(
new MouseAdapter()
{
public void mousePressed(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
component.moveTo(x, y);
}
});
Since individual controllers are usually added to components,
the controller code must have access to a reference to the
component it will control.
There are several possibilities. The example has this organization:
- A separate class for the component which inherits from
JComponent
- An application whose main method:
- creates the component in a final local variable
- defines an inner controller class
- adds the controller to the component
- creates a frame to view the component
- adds the component to the frame
- makes the frame visible
This approach can make the main method become quite
bloated.
Instead of having the main method of the application create the
frame AND create the components AND add listeners to the
components, we can use inheritance to create a subclass
of JFrame that handles these responsibilities.
Then the main application can just create an instance of this
specialized frame and make it visible.
We can revise the classes from the Rectangle example to use a
specialized frame class:
Previous classes:
JComponent
RectangleComponentViewer
The RectangleComponent is a final
local variable in main.
The main method creates the frame, the
RectangleComponent, the controller for it, adds the
component to the frame and makes the
frame visible.
The model, RectangleComponent, is not changed in the revised version.
Most of the responsibilities that were in the main method of
the old RectangleComponentViewer have
been assumed by the new RectangleComponentFrame.
public class RectangleComponentFrame extends JFrame
{
private RectangleComponent component;
public RectangleComponentFrame(int w, int h)
{
// Initialize JFrame parent and set the frame title
super("Rectangle Mover");
// Create a rectangle component
component = new RectangleComponent();
// Create and add a mouse listener for the component
component.addMouseListener(
new MouseAdapter()
{
public void mousePressed(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
component.moveTo(x, y);
}
});
// Add the component to this frame
add(component);
// Set the size of this frame
setSize(w,h);
}
}
public class RectangleComponentViewer
{
private static final int FRAME_WIDTH = 300;
private static final int FRAME_HEIGHT = 400;
public static void main(String[] args)
{
JFrame frame = new RectangleComponentFrame(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Many events generated by a graphical application are events
are caused by a user such as mouse events or typing in a textbox, etc.
In contrast, a program can generate regular timer events by
creating a Timer instance.
ActionListener listener = ...
final int DELAY = 100; // Milliseconds between timer ticks
Timer t = new Timer(DELAY, listener);
t.start();
The second parameter is the object with the event handler
code to be executed each time the timer event occurs.
Once the timer is started - t.start() - it will generate a timer event after
DELAY milliseconds and reset the the timer.
So timer events will
continue to occur once every DELAY milliseconds.
The timer can be stopped: t.stop()
The timer event handler must implement this interface:
public interface ActionListener
{
public void actionPerformed(ActionEvent event);
}
The Timer and TimerListener classes can be used to cause the
Rectangle component to move at each timer event:
class TimerListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
component.moveBy(1, 1);
}
}
-
Inner classes are classes defined inside a class or even
inside a method.
-
An inner class defined in a method can only access local
variables of the method that are declared final.
-
An inner class defined inside a class can access the
members of the containing class.
If a class has a member which needs a controller, it is
convenient to create the controller as an inner class so that
the controller methods will have easy access to the member.
-
A controller class doesn't have to be an inner class, but it would
need to have access to the object it will control.
For example, the RectangleComponentFrame could add a
controller for its RectangleComponent member by passing the
component to the controller's constructor:
// Create a mouse listener for this component
MouseListener listener = new MousePressedListener(component);
component.addMouseListener(listener);
where MousePressedListener is an ordinary class (not an
innner class) that implements the MouseListener interface:
public class MousePressedListener extends MouseAdapter
{
RectangleComponent component;
public MousePressedListener(RectangleComponent component)
{
this.component = component;
}
public void mousePressed(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
component.moveTo(x, y);
}
}