We can get an event. We can paint graphics. But can we paint graphics when we get an event?
Every time you click the button, the circle will change colors. The program is structured as follows:
What Is A GUI Layout?
GUI layouts are covered in the following tutorial, but we’ll do a quick lesson here to get you started.
A frame has five regions that you can add to by default. You can only add one thing to each region of a frame. But do not worry! That one thing could be a panel that holds three other things, including another panel that holds two more things, and so on. In fact, when we added a button to the frame, we were ‘cheating’.
frame.getContentPane().add(button);
This isn’t the way it’s supposed to be done (the one-arg add() method). The right approach is to use the two-args add() method.
frame.getContentPane().add(BorderLayout.CENTER, button);
The two-args add() method takes a region (using a constant) and the widget to add to that region as arguments.
In the following program, each time you click the button, the circle changes color.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GUIDemo3 implements ActionListener {
JFrame frame;
public static void main (String[] args) {
GUIDemo3 guiDemo3 = new GUIDemo3();
guiDemo3.drawGUI();
}
public void drawGUI() {
frame = new JFrame();
JButton button = new JButton("Change color");
button.addActionListener(this);//Add the listener (this) to the button.
MyCustomDrawingPanel drawingPanel = new MyCustomDrawingPanel();
// Add the two widgets (button and drawing panel) to
// the two regions of the frame.
frame.getContentPane().add(BorderLayout.SOUTH, button);
frame.getContentPane().add(BorderLayout.CENTER, drawingPanel);
frame.setSize(400,300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent event) {
/* When the user clicks, tell the frame
to repaint() itself. That means
paintComponent() is called on every
widget in the frame! */
frame.repaint();
}
}
class MyCustomDrawingPanel extends JPanel {
/* The drawing panel's paintComponent() method
is called every time the user clicks.*/
//the code in this method was discussed in RandomCircleDemo
public void paintComponent(Graphics g) {
g.fillRect(0,0,this.getWidth(), this.getHeight());
int redColor = (int) (Math.random() * 256);
int greenColor = (int) (Math.random() * 256);
int blueColor = (int) (Math.random() * 256);
Color randColor = new Color(redColor, greenColor, blueColor);
g.setColor(randColor);
g.fillOval(50,50,100,100);
}
}
Let us try something a little more difficult. Let us put two buttons.
The south button will continue to function as it does now, calling repaint on the frame. The second button (which will be placed in the east region) will alter the text on a label. (A label is simply text on a screen). So now we need FOUR widgets.
And we need to get TWO events.
The UI will look something like this:
The GUI code is pretty simple. Just create A JLabel object and a JButton object and add them to the frame.
1.Creating the widgets
JButton labelButton = new JButton("Change Label");
JLabel label = new JLabel("A simple label!!");
2. Adding widgets to the frame
frame.getContentPane().add(BorderLayout.EAST, labelButton);
frame.getContentPane().add(BorderLayout.WEST, label);
The real problem arises when we attempt to get events from two different buttons. How do you get two events when you have only one actionPerformed() method? How do you get action events for two different buttons, when each button needs to do something different?
Approach 1
You might be tempted to implement two actionPerformed() methods.
class CustomGUI implements ActionListener {
/*
some code goes here
*/
public void actionPerformed(ActionEvent event) {
frame.repaint();
}
public void actionPerformed(ActionEvent event) {
label.setText("The text changed!!");
}
}
The issue is that you cannot do so. In a Java class, you cannot implement the same method twice. It will not compile. Even if you did, how would the event source know which of the two methods to invoke?
Approach 2
You may register the same listener with both buttons.
class CustomGUI implements ActionListener {
// declare a bunch of instance variables here
public void drawGUI() {
// build gui
colorButton = new JButton();
labelButton = new JButton();
colorButton.addActionListener(this);
labelButton.addActionListener(this);
// more gui code here ...
}
public void actionPerformed(ActionEven event) {
if (event.getSource() == colorButton) {
frame.repaint();
} else {
label.setText("The text changed!!");
}
}
}
This works, but it's not very OO in most cases. One event handler doing multiple things means you have a single method doing multiple things. You have to mess with everyone's event handler if you want to change how one source is handled. It is sometimes a good solution, but it usually hurts maintainability and extensibility.
Approach 3
You may create two separate ActionListener classes.
class CustomGUI {
JFrame frame;
JLabel label;
void gui() {
// code to instantiate the two listeners and register one
// with the color button and the other with the label button
}
} // close class
class ColorButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
frame.repaint();
}
}
class LabelButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("The text changed!!");
}
}
These classes won't have access to the variables they need to act on, 'frame' and 'label'. You could fix it, but you'd have to give each of the listener classes a reference to the main GUI class, so that inside the actionPerformed() methods the listener could use the GUI class reference to access the variables of the GUI class. But that's breaking encapsulation, so we'd probably need to make getter methods for the gui widgets (getFrame(), getLabel(), etc.). And you'd probably need to add a constructor to the listener class so that you can pass the GUI reference to the listener at the time the listener is instantiated. And, well, it gets messier and more complicated.
There must be a better way! The solution to this problem is using inner classes. You can nest one class inside another. It’s simple. Just make sure the definition for the inner class is inside the outer class’s curly braces. The next tutorial will go over nested classes.