Java drawing weird visual artifacts/bug - java

I am beginner in java programming. We were given a task, to do some algorithms. I got Sierpinski Triangle. I had an idea to create a 2D array and store the values, 0 = white rectangle, 1 = blue rectangle. I had big trouble to draw it (never had any experience with swing/awt). I finally did it but on the end of the drawing there is weird visual bug. It is not ending but the lines are still continuing.
I have no idea why is it like that.
Here is my code:
Okno class that extends JPanel:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package newpackage;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
*
* #author Juraj
*/
class Okno extends JPanel {
// value = 1 is blue rectangle, value 0 = white rectangle
public static int n = 500;
public static int[][] tabulka = new int[n][n]; //creating 2D array
public static void inicializaciaTabulky(){
for (int i = 0; i < n; i++) { // initialization of 2D array (first column and first row = 1)
tabulka[0][i] = 1;
tabulka[i][0] = 1;
}
}
// filling the rest of the array; if the cell above and cell to the left are the same value, e.g.
// value of 1 or 0, then it is 0, everything else is 1
public static void doplnenieTabulky() {
for (int i = 1; i < n; i++) {
for (int j = 1; j < n; j++) {
if (tabulka[i-1][j] == 1 && tabulka[i][j-1] == 1 ||
tabulka[i-1][j] == 0 && tabulka[i][j-1] == 0) {
tabulka[i][j] = 0;
} else {
tabulka[i][j] = 1;
}
}
}
}
// drawing rectangles; if the value is 1 = blue rectangle, value 0 = white rectangle
private void vykreslenie(Graphics g){
Graphics2D g2d = (Graphics2D) g;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
switch(tabulka[i][j]) {
case 0:
g.setColor(Color.white);
g.drawRect(i, j, 50, 50);
break;
case 1:
g.setColor(Color.blue);
g.drawRect(i, j, 50, 50);
break;
}
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
vykreslenie(g);
}
}
Trojuholnik class that extends JFrame:
public class Trojuholnik extends JFrame {
public Trojuholnik() {
initUI();
}
private void initUI() {
setSize(800, 600);
setTitle("Sierpinski Triangle");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new Okno());
setLocationRelativeTo(rootPane);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Trojuholnik trojuholnik = new Trojuholnik();
trojuholnik.setVisible(true);
Okno.inicializaciaTabulky();
Okno.doplnenieTabulky();
System.out.println(Arrays.deepToString(Okno.tabulka));
}
});
}
}
The current result looks like this:

First of all some basic suggestions:
Don't create a class that extends JFrame. You are not adding new functionality to the frame so you should just be adding your panel to a frame. So the basic code in your run() method should be something like:
JFrame frame = new JFrame();
frame.setTitle("Sierpinski Triangle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new Okno());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Don't use static variables and methods in your Okno class. Just create a constructor for your class and initialize the array in the constructor.
When doing custom painting you should be overriding the getPreferredSize() method of you class to return the size of the component because each component should know its own size. Note how I changed the above code to use the pack() method. Now the frame will be resized properly. So you would add something like the following to your Okno class:
#Override
public Dimension getPreferredSize()
{
return new Dimension(500, 500);
}
Regarding the painting problem. I don't really know what the algorithm is doing, but I suspect that the problem is that your array has 500 values, but the painting of the Rectangle is using a size of 50, so effectively the size of your panel is 550x550 not 500x500 which causes the artifacts.
I think the solution is to simply draw with a rectangle size of (1, 1). At least the drawing looks the same to me.

Related

How can I add a rectangle in BorderLayout.SOUTH?

I am trying to add a thing like this in my music player application in swing.
I tried to add a rectangle to BorderLayout.SOUTH, but it never appeared on screen. Here is what I did:
public class MyDrawPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillRect(200,200,200,200);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
MyDrawPanel a = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.SOUTH,a);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000,1000);
frame.setVisible(true);
}
}
I just did not try 200,200,200,200, but I tried a lot of values, even with the help of a for loop, but it never appeared on screen. If I used CENTER instead of SOUTH it appeared. I read the documentation to check how fillRect works, but it simply said it added x+width and y+height. The point (0,0) is the top left corner. I checked that by adding a rectangle to CENTER layout. How cam I do it?
I did not share the output, because it was just a blank screen.
The values you give to fillRect are wrong. The first two are the top left corner's coordinates, relative to the component you're painting in; in your case the MyDrawPanel. With the code you posted, this drawing area is outside of the container the panel is placed in. You want to do
g.fillRect(0,0,200,200);
A note: You usually want to call frame.pack() after you've finished adding all components, so it can layout itself. In your case, this results in a tiny window because the system doesn't know how large it should be. You probably want to add a method
public Dimension getPreferredSize() {
System.out.println("getting pref size");
return new Dimension(200, 200);
}
to ensure it's always large enough to draw the full rectangle.
Also, you should call frame.getContentPane().setLayout(new BorderLayout()) before. You can print it out without setting it to see it is not the default. EDIT: As VGR points out, the documentation says that it is in fact a BorderLayout. I cannot confirm that is the case - it is in fact a RootLayout. That seems to behave like a BorderLayout though.
I thought this might make a quick little project. Here's the level meter I came up with.
The important parts are the DrawingPanel and the LevelMeterModel. The DrawingPanel takes the information from the LevelMeterModel and paints the bars on a JPanel.
The LevelMeterModel is an int array of levels, a minimum level, and a maximum level. The maximum level could be calculated, but I assumed music has a certain volume and frequency range.
The JFrame holds the DrawingPanel. A Swing Timer varies the levels somewhat randomly. The random numbers are in a small range so the bar heights don't change abruptly.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class LevelMeterGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new LevelMeterGUI());
}
private final DrawingPanel drawingPanel;
private final LevelMeterModel model;
public LevelMeterGUI() {
this.model = new LevelMeterModel();
this.drawingPanel = new DrawingPanel(model);
}
#Override
public void run() {
JFrame frame = new JFrame("Level Meter GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println("Frame size: " + frame.getSize());
Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
model.setRandomLevels();
drawingPanel.repaint();
}
});
timer.start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final int drawingWidth, drawingHeight, margin, rows;
private final Dimension barDimension;
private final LevelMeterModel model;
public DrawingPanel(LevelMeterModel model) {
this.model = model;
this.margin = 10;
this.rows = 20;
this.barDimension = new Dimension(50, 10);
int columns = model.getLevels().length;
drawingWidth = columns * barDimension.width + (columns + 1) * margin;
drawingHeight = rows * barDimension.height + (rows + 1) * margin;
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(drawingWidth, drawingHeight));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int maximum = model.getMaximumLevel();
double increment = (double) maximum / rows;
int peak = rows * 75 / 100;
int x = margin;
for (int level : model.getLevels()) {
int steps = (int) Math.round((double) level / increment);
int y = drawingHeight - margin - barDimension.height;
for (int index = 0; index < steps; index++) {
if (index < peak) {
g.setColor(Color.GREEN);
} else {
g.setColor(Color.RED);
}
g.fillRect(x, y, barDimension.width, barDimension.height);
y = y - margin - barDimension.height;
}
x += margin + barDimension.width;
}
}
}
public class LevelMeterModel {
private final int minimumLevel, maximumLevel;
private int[] levels;
private final Random random;
public LevelMeterModel() {
this.minimumLevel = 100;
this.maximumLevel = 999;
this.levels = new int[8];
this.random = new Random();
setRandomLevels();
}
public void setRandomLevels() {
for (int index = 0; index < levels.length; index++) {
levels[index] = getRandomLevel(levels[index]);
}
}
private int getRandomLevel(int level) {
if (level == 0) {
return random.nextInt(maximumLevel - minimumLevel) + minimumLevel;
} else {
int minimum = Math.max(level * 90 / 100, minimumLevel);
int maximum = Math.min(level * 110 / 100, maximumLevel);
return random.nextInt(maximum - minimum) + minimum;
}
}
public int[] getLevels() {
return levels;
}
public int getMinimumLevel() {
return minimumLevel;
}
public int getMaximumLevel() {
return maximumLevel;
}
}
}

For loop(s) not working as intended with graphics

I've been trying to make a for loop that horizontally replicates vertical lines across the screen, each the same distance apart from its precedent; however, my code doesn't seem to work despite appearing to be syntactically correct. I've posted my code below, hopefully someone can identify the problem.
class HVLines
{
public static void Lines(Graphics g)
{
int k;
int x=0;
for (k = 1; k <= 50; k++)
{
g.drawLine(20+x,150,20+x,525);
for (x = 1; x <= 50; x+=20)
{
}
}
}
}
It should look like this. I can't ensure it will work since I don't know how you use it. Also, in your loops, you use x twice, but don't use k. Change one of those, or , if you don't need it, erase one of the loops.
I am prett sure this one creates a diagonal line, not horizontal.
for (k = 1; k <= 50; k++) {
for (x = 1; x <= 50; x+=20) {
// Choose one of the following
g.drawLine(20+k,150,20+x,525);
g.drawLine(20+x,150,20+k,525);
}
}
This one creates a straight line:
for (x = 1; x <= 50; x+=20) {
g.drawLine(20+x,150,20+x,525);
}
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import javax.swing.JApplet;
import javax.swing.JFrame;
public class DrawLine extends JApplet {
public void init() {
setBackground(Color.white);
setForeground(Color.white);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.gray);
for (int y = 0; y <= 300; y += 30) {
g2.draw(new Line2D.Double(0, y, 300, y));
}
}
public static void main(String s[]) {
JFrame f = new JFrame("Line");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JApplet applet = new DrawLine();
f.getContentPane().add("Center", applet);
applet.init();
f.pack();
f.setSize(new Dimension(300, 300));
f.setVisible(true);
}
}

Game In Java Not Adding JLabel With Icon

I am developing a basic game in Java that is going to be similar to 2d games where you dodge asteroids in a space ship.
My issue is that for some reason some of my asteroids are not correctly being added to my JFrame.
Here is how I add my JLabel's (asteroids):
NOTE: SpriteSheet.java is a valid and functioning class that does successfully return the image.
#SuppressWarnings("serial")
class Entity extends JLabel {
private int asteroidSizeW = 72;
private int asteroidSizeH = 72;
private int entities = 5;
public Entity() {
List<Integer> asteroidLocations = new ArrayList<Integer>();
Random ran = new Random();
for (int i = 0; i < entities; i++) {
if (asteroidLocations.size() > 0) {
for (int j = 0; j < asteroidLocations.size(); j++) {
int chosenSpawn = ran.nextInt(Frame.WIDTH - 72);
if (chosenSpawn > (asteroidLocations.get(j) + 72) || chosenSpawn < (asteroidLocations.get(j) - 72)) {
System.out.println("Made it!");
System.out.println(asteroidLocations);
asteroidLocations.add(chosenSpawn);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(chosenSpawn, 0);
setSize(asteroidSizeW, asteroidSizeH);
break;
}
}
} else {
int x = ran.nextInt(Frame.WIDTH - 72);
asteroidLocations.add(x);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(x, 0);
setSize(asteroidSizeW, asteroidSizeH);
}
}
}
}
After testing I have found out that the first JLabel is being added (the else in):
if (asteroidLocations.size() > 0) {
} else {
// this is being executed once (like it should)
}
In other words, the first image (JLabel) does display in the game.
Everything else does work as intended as seen here by the output:
Made it!
[181]
Made it!
[181, 273]
Made it!
[181, 273, 452]
Made it!
[181, 273, 452, 627]
It seems like it has an issue returning the JLabel inside the nested loop.
Could anybody please help me out? Thanks.
FULL CLASS:
package main;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
class Frame extends JFrame {
public static final int WIDTH = 800;
public static final int HEIGHT = 800;
private String TITLE = "Asteroid Killer";
public static void main(String args[]) {
Frame f = new Frame();
f.createFrame();
f.showFrame();
}
private void createFrame() {
setTitle(TITLE);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
add(new MainPanel());
setSize(WIDTH, HEIGHT);
}
private void showFrame() {
this.setVisible(true);
}
}
#SuppressWarnings("serial")
class MainPanel extends JPanel {
public MainPanel() {
setLayout(null);
setBackground(Color.GRAY);
//add(new Minimap());
add(new Entity());
}
}
/*#SuppressWarnings("serial")
class Minimap extends JPanel {
public Minimap() {
setBounds(284, 0, 100, 100); // x,y,width,height
setBackground(Color.RED);
}
}*/
#SuppressWarnings("serial")
class Entity extends JLabel {
private int asteroidSizeW = 72;
private int asteroidSizeH = 72;
private int entities = 5;
public Entity() {
List<Integer> asteroidLocations = new ArrayList<Integer>();
Random ran = new Random();
for (int i = 0; i < entities; i++) {
if (asteroidLocations.size() > 0) {
for (int j = 0; j < asteroidLocations.size(); j++) {
int chosenSpawn = ran.nextInt(Frame.WIDTH - 72);
if (chosenSpawn > (asteroidLocations.get(j) + 72) || chosenSpawn < (asteroidLocations.get(j) - 72)) {
System.out.println("Made it!");
System.out.println(asteroidLocations);
asteroidLocations.add(chosenSpawn);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(chosenSpawn, 0);
setSize(asteroidSizeW, asteroidSizeH);
break;
}
}
} else {
int x = ran.nextInt(Frame.WIDTH - 72);
asteroidLocations.add(x);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(x, 0);
setSize(asteroidSizeW, asteroidSizeH);
}
}
}
}
You logic is around backwards. You are only ever adding a single JLabel to your container...
add(new Entity());
You actually Entity class has no means to put anything onto the screen other than a single representation of itself...which is typically the last thing it created within it's for loop...
Instead of trying to create multiple entities within your Entity class (I know, you're only setting the properties, but the intention is the same), move the creation process out to the the MainPanel and physically create a new Entity on each loop
Now, having said that, screwing with components in this manner is probably not the best solution. You should consider using a custom painting approach, you will find it easier to manage all the entities and will probably also consume less overhead...
Consider having a look at
Painting in AWT and Swing
Performing Custom Painting
for more details

Can't set maximum size of connect 4 grid in Java swing

Right now I am trying to make it so that the connect 4 grid on the gui is always a 7x8 no matter what window size. I have been trying to set the button array with a setMaximumSize and it's not working.
Here is the code that sets the JButton array
void ResetGame()
{
JLabel label = new JLabel("Click a column to drop piece");
for(int r=0;r<gameBoard.length;r++)
{
java.util.Arrays.fill(gameBoard[r],0,gameBoard[r].length,'0');
//loop through board columns
for(int c=0;c<gameBoard[r].length;c++)
{
gameButtons[r][c]= new JButton(empty);
panel.add(gameButtons[r][c]);
gameButtons[r][c].setPreferredSize(new Dimension(70,70));
//Allows buttons to be arranged as grid.
GridLayout grid = new GridLayout(0,8);
//Sets into grid.
gameButtons[r][c].setLayout(grid);
gameButtons[r][c].setMaximumSize(new Dimension(0,10));
}
panel.add(label);
}
// loop through array setting char array back to ' ' and buttons array back to empty pic
// reset currentPlayer and numMoves variables
}
Just in case I'll also include the window creation method here.
public void CreateWindow()
{
//Sets window title and create window object.
JFrame aWindow = new JFrame("Connect Four");
//Set window position and size
aWindow.setBounds(200,100,600,800);
//What close button does.
aWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Make window visible.
aWindow.setVisible(true);
//Sets content area to work with stuff.
aWindow.setContentPane(panel);
//Gets content pane.
Container content = aWindow.getContentPane();
}
Not sure of what you are trying to achieve with setMaximumSize. Without explicit and precise requirements, we can hardly help you.
So, I would suggest that you take a look at the following snippet (which is an SSCCE) and try to find out what you are doing wrong:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Connect4 {
public class GameButton extends JPanel {
private final int row;
private final int column;
private Color color;
public GameButton(final int row, final int column) {
super();
this.row = row;
this.column = column;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Game button " + row + " " + column + " has been pressed");
}
});
}
public void setColor(Color color) {
this.color = color;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int size = Math.min(getWidth(), getHeight());
int offset = (int) ((double) size / 10);
size = size - 2 * offset;
if (color == null) {
g.setColor(Color.BLACK);
g.drawOval(offset, offset, size, size);
} else {
g.setColor(color);
g.fillOval(offset, offset, size, size);
}
}
}
protected void initUI() {
JPanel gridPanel = new JPanel(new GridLayout(7, 8));
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 8; j++) {
GameButton gameButton = new GameButton(i, j);
gridPanel.add(gameButton);
}
}
JFrame frame = new JFrame(Connect4.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(gridPanel);
frame.setSize(600, 600);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Connect4().initUI();
}
});
}
}
setMaximumSize() puts a bound on how large something can be. Depending on what you are using for a layout manager you want either setPreferredSize() or setSize().

java swing: in paintComponent method how to know what to repaint?

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.

Categories