Java Graphics Interface III
paintComponent - 2020
When we want to draw our own graphics on the screen, we should put our graphics code inside the paintComponent() method. But we never call this method directly. The system calls it.
A Graphics object is the argument to this method. It is the actual drawing canvas that will be displayed. We should give it to the system. However, we can ask the system to refresh the display by calling repaint(). The repaint() call eventually leads to paintComponent() being called.
In the following code, we make a subclass of JPanel and override one method, paintComponent().
Applications that have components with any custom rendering may need to override this method to perform that custom rendering. This rendering may include drawing graphics inside a canvas, but it also include doing anything custom to a standard component, such as rendering a gradient for the background of a button. Standard Swing components already handle this functionality for their graphics, so it is only for the case of custom components with specialized rendering that this method must be overridden.
Overriding paintComponent() is arguably the most important concept to understand in writing custom Swing components.
import java.awt.*; import javax.swing.*; public class OvalPaint extends JPanel { public void paintComponent(Graphics g) { g.setColor(Color.orange); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); g.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); } public static void main(String args[]) { JFrame frame = new JFrame("OvalPaint"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); OvalPaint panel = new OvalPaint(); frame.add(panel); frame.setSize(300, 200); frame.setVisible(true); } }
public class OvalPaint extends JPanel {
Here, we're making a subclass of JPanel which is a widget that we can add to a frame:
JFrame frame = new JFrame("OvalPaint"); ... OvalPaint panel = new OvalPaint(); frame.add(panel);
The following block of code Graphics method which is the key in this section. We'll never call this directly. The system calls it:
public void paintComponent(Graphics g) { ... }
Inside that block of code, we have:
public void paintComponent(Graphics g) { g.setColor(Color.orange); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); g.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); }
The Graphics.g is a kind of painting tool. We're telling it what color to paint with and then what shape to paint, where it goes, and how big it is.
The argument for paintComponent() is a type Graphics which is from java.awt.Graphics:
public void paintComponent(Graphics g) {}
The parameter g is a Graphics object. Actually, the object referenced by g is an instance of the Graphics2D class.
So, if we need to use a method from the Graphics2D class, we can' use the g in paintComponent(Graphics g) directly. However, we can cast it with a new Graphics2D variable:
Graphics2D graphics2d = (Graphics2D) g;
Why do we care?
That's because there are things we can do with a Graphics2D reference that we can't do with a Graphics reference. Actually, a Graphics2D object can do more than a Graphics object.
Here are the methods we can call on a Graphics reference:
- drawImage()
- drawLine()
- drawPolygon()
- drawRect()
- drawOval()
- fillRect()
- fillOval()
- fillRoundRect()
- setColor()
Here are the methods we can call on a Graphics2D object:
- fill3DRect()
- draw3DRect()
- rotate()
- scale()
- shear()
- transform()
- setRenderingHints()
import java.awt.*; import javax.swing.*; public class OvalPaint extends JPanel { public void paintComponent(Graphics g) { Graphics2D graphics2d = (Graphics2D)g; graphics2d.setColor(Color.orange); graphics2d.fillRect(0, 0, getWidth(), getHeight()); GradientPaint gradient = new GradientPaint ( getWidth()/4, getHeight()/4, Color.red, getWidth()*3/4, getHeight()*3/4, Color.orange); graphics2d.setPaint(gradient); graphics2d.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); } public static void main(String args[]) { JFrame frame = new JFrame("OvalPaint with Gradient"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); OvalPaint panel = new OvalPaint(); frame.add(panel); frame.setSize(300, 200); frame.setVisible(true); } }
The following example has a drawing panel at the center with two buttons to control the shape and color. When we click one of the two buttons, however, the program does not know what we want. That's because we have only one actionPerformed(ActionEvent event) method. Clearly, there is a problem.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class WhichOne implements ActionListener { JFrame frame; public static void main(String args[]) { WhichOne w = new WhichOne(); w.go(); } public void go() { frame = new JFrame("Which One?"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MyDrawing drawPanel = new MyDrawing(); JButton colorButton = new JButton("Color"); JButton shapeButton = new JButton("Shape"); colorButton.addActionListener(this); shapeButton.addActionListener(this); frame.getContentPane().add(BorderLayout.WEST, colorButton); frame.getContentPane().add(BorderLayout.CENTER, drawPanel); frame.getContentPane().add(BorderLayout.EAST, shapeButton); frame.setSize(300, 200); frame.setVisible(true); } public void actionPerformed(ActionEvent event) { frame.repaint(); } } class MyDrawing extends JPanel { public void paintComponent(Graphics g) { g.setColor(Color.orange); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); g.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); } }
How do we get action events for two different buttons so that each button can do perform its own task?
The following code register the same listener for both buttons:
String messageColor = "Color Changed"; String messageShape = "Shape Changed"; ... colorButton.addActionListener(this); shapeButton.addActionListener(this);
It's querying the event object to find out which button has been clicked:
public void actionPerformed(ActionEvent event) { if(event.getSource() == colorButton) { System.out.println(messageColor); } else { System.out.println(messageShape); } frame.repaint(); }
This works!
However, when we need to change the task for one event, we are actually touching the task for other events, because the one event handler doing things for others as well. So, it has some issues related to the maintainability and extensibility.
The following code create two separate ActionListener classes. It instantiate the two listeners and register one with the color button and the other with the shape button.
So, instead of pass this to the button's listener registration method, we pass a new instance of the new class:
colorButton.addActionListener(new ColorButtonListener()); shapeButton.addActionListener(new ShapeButtonListener());
However, we cannot use the reference variables such as messageColor and messageShape of the WhichOne class. These classes (ColorButtonListener and ShapeButtonListener) won't have access to the variables they need to act on, in the example, messages.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class WhichOne { JFrame frame; JButton colorButton; JButton shapeButton; String messageColor = "Color Changed"; String messageShape = "Shape Changed"; public static void main(String args[]) { WhichOne w = new WhichOne(); w.go(); } public void go() { frame = new JFrame("Which One?"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MyDrawing drawPanel = new MyDrawing(); colorButton = new JButton("Color"); shapeButton = new JButton("Shape"); colorButton.addActionListener(new ColorButtonListener()); shapeButton.addActionListener(new ShapeButtonListener()); frame.getContentPane().add(BorderLayout.WEST, colorButton); frame.getContentPane().add(BorderLayout.CENTER, drawPanel); frame.getContentPane().add(BorderLayout.EAST, shapeButton); frame.setSize(300, 200); frame.setVisible(true); } } class ColorButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println(messageColor); } } class ShapeButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println(messageShape); } } class MyDrawing extends JPanel { public void paintComponent(Graphics g) { g.setColor(Color.orange); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); g.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); } }
The access problem in the previous example can be resolved by using inner classes. By putting two ActionListers inside the WhichOne classes, we have an access to the reference variables.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class WhichOne { JFrame frame; JButton colorButton; JButton shapeButton; String messageColor = "Color Changed"; String messageShape = "Shape Changed"; public static void main(String args[]) { WhichOne w = new WhichOne(); w.go(); } public void go() { frame = new JFrame("Which One?"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MyDrawing drawPanel = new MyDrawing(); colorButton = new JButton("Color"); shapeButton = new JButton("Shape"); colorButton.addActionListener(new ColorButtonListener()); shapeButton.addActionListener(new ShapeButtonListener()); frame.getContentPane().add(BorderLayout.WEST, colorButton); frame.getContentPane().add(BorderLayout.CENTER, drawPanel); frame.getContentPane().add(BorderLayout.EAST, shapeButton); frame.setSize(300, 200); frame.setVisible(true); } class ColorButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println(messageColor); } } class ShapeButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println(messageShape); } } } class MyDrawing extends JPanel { public void paintComponent(Graphics g) { g.setColor(Color.orange); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.red); g.fillOval(getWidth()/4, getHeight()/4, getWidth()/2, getHeight()/2); } }
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization