I am creating a basic Tic-Tac-Toe application in Java Swing, and for this purpose, I have started investigating drawing. However, I have encountered an issue when a subclass of JPanel contains more than one instance of a subclass of JLabel, which overrides the paintComponent(Graphic) method, in a GridLayout array format embedded in itself. The issue is that only the first element in this array is painted.
An example for the customized subclass of JLabel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class DrawLabel extends JLabel {
private boolean hasPaint;
public DrawLabel() {
super();
this.hasPaint = false;
}
public void draw() {
hasPaint = true;
repaint();
}
public void clear() {
hasPaint = false;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (hasPaint) {
g2.drawOval(getX(), getY(), getWidth(), getHeight());
} else {
g2.clearRect(getX(), getY(), getWidth(), getHeight());
}
}
}
An example for the customized subclass of JPanel:
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class DrawPanel extends JPanel {
private Dimension size;
private DrawLabel[][] fields;
public DrawPanel(Dimension d) {
super();
this.size = d;
this.fields = new DrawLabel[size.height][size.width];
this.setLayout(new GridLayout(size.height, size.width));
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
this.fields[i][j] = new DrawLabel();
this.add(fields[i][j]);
}
}
}
public void draw(int row, int col) {
fields[row][col].draw();
}
public void clear() {
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
fields[i][j].clear();
}
}
}
}
An example of an issue-provoking scenario:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Driver {
public static void main(String[] args) {
DrawPanel board = new DrawPanel(new Dimension(2, 1));
JFrame canvas = new JFrame();
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas.add(board, BorderLayout.CENTER);
canvas.pack();
canvas.setLocationRelativeTo(null);
canvas.setVisible(true);
board.draw(0, 0); // This gets painted.
board.draw(0, 1); // This does not get painted!
}
}
Curiously, if the DrawPanel.draw(int, int) is changed to set the text of the element rather than draw upon it, only the second element is updated, while the first is not, which is the exact opposite of the original problem.
I have tried to look up other issues and questions related to painting of subcomponents, but I have yet to find an issue like this one, where it seems that every instance beyond the first in the GridLayout are not drawable in the same way. What could be wrong?
Thank you for your time and effort!
g2.drawOval(getX(), getY(), getWidth(), getHeight());
Painting of a component is done relative to the component, not the panel the component is painted in. The getX/Y() methods return the location of the component relative to the parent component.
So if the size of each component is (200, 200) then the oval of the first component will be painted using
g2.drawOval(0, 0, 200, 200);
The second component will be painted at:
g2.drawOval(200, 0, 200, 200);
but since the component is only 200 pixels wide, the start x location of 200 is outside the bounds of the component so there is nothing to paint.
Instead just use:
g2.drawOval(0, 0, getWidth(), getHeight());
Related
I have a GUI with a panel where I draw. Whatever mouse pattern I do is repeated in every sector which is divided by two lines. However, I am able to do this because my paintComponen method does not invoke super.paintComponent. If I actually do invoke the method I get only a single point whenever i drag my mouse. How should i go about it?
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public final class DisplayPanel extends JPanel
{
private boolean dragging;
private Point draw;
private Line2D sectorLine;
private int sectors;
public void init()
{
DisplayListener listener = new DisplayListener();
addMouseListener(listener);
addMouseMotionListener(listener);
setOpaque(true);
setBackground(Color.BLACK);
setSize(900,900);
setVisible(true);
}
//performs the drawing on the display panel
public void paintComponent(Graphics g)
{
//super.paintComponent(g);
setBackground(Color.BLACK);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
sectorLine = new Line2D.Double(getWidth()/2, 0, getWidth()/2, getHeight());
sectors = 12;
//draws the sectors on the screen
for(int i=0; i<sectors; i++)
{
g2d.draw(sectorLine);
g2d.rotate(Math.toRadians(30),getWidth()/2,getHeight()/2);
}
//draws the doilie
if(dragging)
{
for(int i=0; i<sectors; i++)
{
g2d.fillOval((int) draw.getX(), (int) draw.getY(),20, 20);
g2d.rotate(Math.toRadians(30), getWidth()/2, getHeight()/2);
}
}
}
private class DisplayListener extends MouseAdapter
{
public void mouseDragged(MouseEvent event)
{
dragging = true;
draw = event.getPoint();
repaint();
}
public void mouseReleased(MouseEvent event)
{
dragging = false;
}
}
}
super.paintComponent() erases/clears the area before drawing, so you only see the point you current draw.
If you want to draw the line the mouse dragged, you would have to store each drawn coordinate in a List, and then in paintComponent(), draw all the points again. Please be aware that this list could get very big, and thus eat a lot of memory, so you should probably think about limiting it somehow.
I have a problem with JApplet. The code was working just fine, but when I converted it from JFrame to JApplet, the render part stopped working properly. Basicly what I'm trying to do is simplistic draw app. When launching applet, half of time repaint() is not working (There is no gray background; you have to put mouse over button for it to update its color etc), furtheremore the pixel rendering part is not shown up at all. Here's the code:
The Frame class (JApplet)
package painter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Frame extends JApplet {
public JPanel panel;
private JButton plus, minus, buttonColor;
private int scaleSize;
private JLabel labelScale;
private final Timer updateTimer;
private static boolean painting = false;
public static Color currentColor;
public static int mode = 0;
// 0 = draw; 1 = setcolor; 2 = erase
private ArrayList<Pixel> pixelArray;
public Frame() {
pixelArray = new ArrayList<>();
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
pixelArray.add(new Pixel(i, j));
}
}
setLayout(new BorderLayout());
panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//g.fillRect(10, 10, 100, 100); <- test if fillRect works at all. Yus it does.
for (int i = 0; i < pixelArray.size(); i++) {
pixelArray.get(i).render(g);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
};
//panel.setBounds(0, 0, 800, 800);
//add(panel);
getContentPane().add(panel);
//panel.setLayout(null);
//panel.setOpaque(true);
//panel.setDoubleBuffered(true);
currentColor = Color.yellow;
buttonColor = new JButton("Choose color");
buttonColor.setBounds(10, 10, 128, 64);
buttonColor.setBackground(currentColor);
buttonColor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentColor = JColorChooser.showDialog(null, "JColorChooser Sample", Color.gray);
}
});
updateTimer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonColor.setBackground(currentColor);
repaint();
}
});
updateTimer.start();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
painting = true;
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
painting = false;
}
});
panel.add(buttonColor);
repaint();
}
public static boolean getPaint() {
return painting;
}
public static void main(String[] args) {
new Frame();
}
}
And here is the Pixel class:
package painter;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.MouseInfo;
import java.awt.Point;
public class Pixel {
private Color color;
private int size;
private int x, y, relativex, relativey;
public Pixel(int relx, int rely) {
color = new Color(0x999999, false);
size = 32;
x = relx * size + 64;
y = rely * size + 64;
}
public boolean mouseOver() {
Point pos, mousepos;
pos = new Point(x, y);
mousepos = MouseInfo.getPointerInfo().getLocation();
if ((mousepos.x > pos.x)
&& (mousepos.x < pos.x + size)
&& (mousepos.y > pos.y)
&& (mousepos.y < pos.y + size)) {
return true;
} else {
return false;
}
}
public void render(Graphics g) {
g.setColor(color);
if (mouseOver() && Frame.getPaint()) {
if (Frame.mode == 0) {
color = Frame.currentColor;
}
if (Frame.mode == 1) {
Frame.currentColor = color;
}
if (Frame.mode == 2) {
color = new Color(0xffffffff, true);
}
}
g.fillRect(x, y, size, size);
if (mouseOver()) {
g.setColor(Color.black);
g.drawRect(x, y, size - 1, size - 1);
g.setColor(Color.yellow);
g.drawRect(x + 1, y + 1, size - 3, size - 3);
}
//g.fillRect(10, 10, 250, 250);
}
}
As a stab in the dark, don't call Graphics#dipose on a Graphics context you did not create yourself explicitly
Apart from the fact the the Graphics context is a shared resource, used by all the components that might need to be painted within a given paint cycle, it can also prevent what ever was painted to it to be displayed on some platforms
In 15 years of professional development, I've never had reason to call Toolkit.getDefaultToolkit().sync();. I doubt it'll make that big a difference, I'm just saying
Java applets provide us with these methods
[here] http://docs.oracle.com/javase/tutorial/deployment/applet/appletMethods.html
the method
public void paint(Graphics g){}
is used as alternative of
public void paintComponent(Graphics g){}
of swing.
Check out http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html for the recommended way to perform custom painting of swing components
I hope my first post isn't too basic for y'all.
I'm trying to do some per-pixel drawing on a JCanvas using a BufferedImage (using setRGB()). I thought I would test all was working with a single diagonal line from the origin to the width/height of the JCanvas. The trouble is that I get a strange offset in the x axis that I can't seem to fix!
Here's a link to the problem:
http://i811.photobucket.com/albums/zz31/bohngy/problemMandel_zpsae20713a.jpeg
Here's the code:
public class Mandelbrot extends JFrame {
private BufferedImage I;
public Mandelbrot() {
super("Mandelbrot Set");
setSize(600, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < getHeight(); x++) {
for (int y = 0; y < getWidth(); y++) {
I.setRGB(x, x, Color.GREEN.getRGB());
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(I, 0, 0, this);
}
public static void main(String[] args) {
new Mandelbrot().setVisible(true);
}
}
General issues
Don't extend JFrame (particularly, don't override the paint method of JFrame). Instead, do the painting in the paintComponent method a class that extends JPanel
Create the GUI from the Event Dispatch Thread
The main reason for the unexpected result is that you are creating an image that has the size of the frame - but the frame also has a title bar and a border, and these are "covering" parts of the image. The size of the area that is actually available for painting is smaller than the total frame size. Additionally, the getWidth() and getHeight() methods may return garbage as long as the frame is not yet visible on the screen.
One approach considering all this could look like in this snippet:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Mandelbrot
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1, 1));
BufferedImage image = createImage(500, 500);
ImagePanel imagePanel = new ImagePanel(image);
frame.getContentPane().add(imagePanel);
frame.pack();
frame.setVisible(true);
}
private static BufferedImage createImage(int w, int h)
{
BufferedImage image = new BufferedImage(w, h,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < w; x++)
{
image.setRGB(x, x, Color.GREEN.getRGB());
}
return image;
}
static class ImagePanel extends JPanel
{
private final BufferedImage image;
ImagePanel(BufferedImage image)
{
this.image = image;
}
#Override
public Dimension getPreferredSize()
{
if (super.isPreferredSizeSet())
{
return super.getPreferredSize();
}
return new Dimension(image.getWidth(), image.getHeight());
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
}
All BufferedImage objects have an upper left corner coordinate of (0, 0). Any Raster used to construct a BufferedImage must therefore have minX=0 and minY=0.
Therein lies your problem.
JavaDoc for BufferedImage
Edit:
Also remove this from your loop:
for (int y = 0; y < getWidth(); y++) {
I.setRGB(x, x, Color.GREEN.getRGB());
}
I am trying to relocate a rectangle but for I cannot figure out why it stays in the same location.
It creates a red rectangle but does not change color or move to a new location.
Here is my code:
package grap_prj.dom.shenkar;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class graphic_main extends JPanel{
static Rectangle rec = new Rectangle ();
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
rec.setSize(10, 10);
rec.setLocation(10, 10);
g2d.setColor(Color.RED);
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void update_ui (Graphics g)
{
System.out.println("in update");
rec.setLocation(50, 50);
Graphics2D g2d = (Graphics2D) g;
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Simple Graphics");
frame.add(new graphic_main());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
graphic_main.update_ui(frame.getGraphics());
frame.revalidate();
}
}
Update:
I have made a few changes in the code, but still the same situation. I change the location but a new rectangle is added instead of moving the existing one.
You are continuosly setting location at 10,10 so the rectangle will always be drawn at 10,10.
After setting location 50,50 you aren't drawing anything. Next step you will set 10,10 again.
Whenever you override paintComponent(), you need to call super.paintComponent().
You are also calling repaint() from repaint(). You need to decide on some action that will cause it to repaint.
You should never call update() or repaint() inside of a paintComponent(...) method. Ever. This risks recursion or ineffective uncontrolled animation.
Don't change the state of your object inside of a paint or paintComponent method. You don't have full control over when or even if these methods get called.
Don't forget to call the super's method inside your paintComponent override to allow the JPanel to do its housekeeping graphics including erasing old dirty pixels.
Even though you change the Graphics context's Color to blue, it will change right back to red anytime the paintComponent is called. So your color change is futile code. Solution: make the Color a variable that can be set.
If you want to do Swing animation, use a Swing Timer.
For an example of Swing animation, have a look at my example here.
For another example, have a look at this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class SimpleAnimation extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Color[] COLORS = { Color.red, Color.orange,
Color.yellow, Color.green, Color.blue, Color.magenta };
private static final int RECT_WIDTH = 40;
private static final int TIMER_DELAY = 10;
private int x = 0;
private int y = 0;
private int colorIndex = 0;
private Color color = COLORS[colorIndex];
public SimpleAnimation() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
if (x + RECT_WIDTH > getWidth()) {
x = 0;
}
if (y + RECT_WIDTH > getHeight()) {
y = 0;
}
if (x % 40 == 0) {
colorIndex++;
colorIndex %= COLORS.length;
color = COLORS[colorIndex];
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SimpleAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SimpleAnimation());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Your changing the location before the frame gets the chance to render it. So its creating it at 10, 10 and then when its rendered changes it to 50 50 then to 10, 10.
My component is bigger than the screen and parts of it are not shown (I will use scrollbars).
When I receive a call in paintComponent(g) how do I know what area should I paint?
I'm not sure if this is what you mean, but the problem is you will have to call repaint() on the JScrollPane each time you get a call in paintComponent(Graphics g) of the JPanel or else updates on the JPanel will not be visible in the JScrollPane.
Also I see you want to use JScrollBar (or maybe you confused the terminology)? I'd recommend a JScrollPane
I made a small example which is a JPanel with a grid that will change its colour every 2 seconds (Red to black and vice versa). The JPanel/Grid is larger then the JScrollPane; regardless we have to call repaint() on the JScrollPane instance or else the grid wont change colour:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().createAndShowUI();
}
});
}
private void createAndShowUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents(frame);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setVisible(true);
}
private void initComponents(JFrame frame) {
JScrollPane jsp = new JScrollPane();
jsp.setViewportView(new Panel(800, 800, jsp));
frame.getContentPane().add(jsp);
}
}
class Panel extends JPanel {
private int across, down;
private Panel.Tile[][] tiles;
private Color color = Color.black;
private final JScrollPane jScrollPane;
public Panel(int width, int height, JScrollPane jScrollPane) {
this.setPreferredSize(new Dimension(width, height));
this.jScrollPane = jScrollPane;
createTiles();
changePanelColorTimer();//just something to do to check if its repaints fine
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
g.setColor(color);
for (int k = 0; k < 5; k++) {
g.drawRect(tiles[i][j].x + k, tiles[i][j].y + k, tiles[i][j].side - k * 2, tiles[i][j].side - 2 * k);
}
}
}
updateScrollPane();//refresh the pane after every paint
}
//calls repaint on the scrollPane instance
private void updateScrollPane() {
jScrollPane.repaint();
}
private void createTiles() {
across = 13;
down = 9;
tiles = new Panel.Tile[across][down];
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
tiles[i][j] = new Panel.Tile((i * 50), (j * 50), 50);
}
}
}
//change the color of the grid lines from black to red and vice versa every 2s
private void changePanelColorTimer() {
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (color == Color.black) {
color = Color.red;
} else {
color = Color.black;
}
}
});
timer.setInitialDelay(2000);
timer.start();
}
private class Tile {
int x, y, side;
public Tile(int inX, int inY, int inSide) {
x = inX;
y = inY;
side = inSide;
}
}
}
In the Panel class if we comment the line updateScrollPane(); in paintComponent(Graphics g) we wont see the grid change colour.
You can find out the area that actually has to be painted by querying the clip bounds of the Graphics object.
The JavaDoc seems to be a bit out-dated for this method: It says, that it may return a null clip. However, this is obviously never the case (and other Swing classes also rely on the clip never being null!).
The follwing MCVE illustrates the difference between using a the clip or painting the whole component:
It contains a JPanel with a size of 800x800 in a scroll pane. The panel paints a set of rectangles, and prints how many rectangles have been painted.
One can use the "Use clip bounds" checkbox to enable and disable using the clip. When the clip is used, only the visible area of the panel is repainted, and the number of rectangles is much lower. (Note that the test whether a rectangle has to be painted or not is rather simple here: It only performs an intersection test of the rectangle with the visible region. For a real application, one would directly use the clip bounds to find out which rectangles have to be painted).
This example also shows some of the tricky internals of scroll panes: When the blinking is switched off, and the scroll bars are moved, one can see that - although the whole visible area changes - only a tiny area actually has to be repainted (namely the area that has become visible due to the scrolling). The other part is simply moved as-it-is, by blitting the previous contents. This behavior can be modified with JViewport.html#setScrollMode.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class PaintRegionTest
{
public static void main(String[] args) throws Exception
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final PaintRegionPanel paintRegionPanel = new PaintRegionPanel();
paintRegionPanel.setPreferredSize(new Dimension(800, 800));
final Timer timer = new Timer(1000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.changeColor();
}
});
timer.setInitialDelay(1000);
timer.start();
JScrollPane scrollPane = new JScrollPane(paintRegionPanel);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JPanel controlPanel = new JPanel(new FlowLayout());
final JCheckBox blinkCheckbox = new JCheckBox("Blink", true);
blinkCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if (blinkCheckbox.isSelected())
{
timer.start();
}
else
{
timer.stop();
}
}
});
controlPanel.add(blinkCheckbox);
final JCheckBox useClipCheckbox = new JCheckBox("Use clip bounds");
useClipCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.setUseClipBounds(
useClipCheckbox.isSelected());
}
});
controlPanel.add(useClipCheckbox);
frame.getContentPane().add(controlPanel, BorderLayout.SOUTH);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class PaintRegionPanel extends JPanel
{
private Color color = Color.BLACK;
private boolean useClipBounds = false;
void setUseClipBounds(boolean useClipBounds)
{
this.useClipBounds = useClipBounds;
}
void changeColor()
{
if (color == Color.BLACK)
{
color = Color.RED;
}
else
{
color = Color.BLACK;
}
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(color);
Rectangle clipBounds = g.getClipBounds();
Rectangle ownBounds = new Rectangle(0,0,getWidth(),getHeight());
System.out.println("clipBounds: " + clipBounds);
System.out.println(" ownBounds: " + ownBounds);
Rectangle paintedRegion = null;
if (useClipBounds)
{
System.out.println("Using clipBounds");
paintedRegion = clipBounds;
}
else
{
System.out.println("Using ownBounds");
paintedRegion = ownBounds;
}
int counter = 0;
// This loop performs a a simple test see whether the objects
// have to be painted. In a real application, one would
// probably use the clip information to ONLY create the
// rectangles that actually have to be painted:
for (int x = 0; x < getWidth(); x += 20)
{
for (int y = 0; y < getHeight(); y += 20)
{
Rectangle r = new Rectangle(x + 5, y + 5, 10, 10);
if (r.intersects(paintedRegion))
{
g.fill(r);
counter++;
}
}
}
System.out.println("Painted "+counter+" rectangles ");
}
}
An aside: For many application cases, such an "optimization" should hardly be necessary. The painted elements are intersected against the clip anyhow, so one will probably not gain much performance. When "preparing" the elements to be painted is computationally expensive, one can consider this as one option. (In the example, "preparing" refers to creating the Rectangle instance, but there may be more complicated patterns). But in these cases, there may also be more elegant and easier solutions than manually checking the clip bounds.
All answers are wrong. So I decided to answer the question despide the fact that the question is two years old.
I believe that the correct answer is calling g.getClipBounds() inside of paintComponent(Graphics g) method. It will return the rectangle in the control's coordinate system of the area which is invalidated and must be redrawn.