I have JSrollPane with a JPanel as the ViewPort component. On this JPanel I use paintComponent to draw a grid, of 64x64px squares. The JPanel is quite large, 28'672px by 14'336px, still the grid is drawn instantly and all seems fine. The problem is that scrolling vertically or horizontally causes the CPU usage to jump up quite high, the faster I scroll the higher it goes. The CPU usage gets up to between 35-50% while scrolling. Scrolling the same sized JPanel without the grid drawn on it, uses very little CPU, so the grid is certainly the cause of the problem. This grid is the most basic part of what I am planning to do inside the scrollpane, if it performs bad now, I fear it will be unusable after more "content" is added.
My question(s) Why does it use so much CPU to scroll over this grid, is the grid getting repainted every time there is a change in the scrollbar's position? Is there a better or more efficient way to draw a scrollable grid?
I had an idea of only drawing the grid of the visible area(by coord), and then redrawing that visible area when the scrollbars are moved, but this would be calling repaint alot. If possible, I'd like to draw the entire grid at startup, and then only repaint on command.
Here is a barebones working example of my JPanel grid.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
public class GridTest extends JFrame
{
static JScrollPane scrollPane;
static JPanel contentPane,gridPane;
public static void main(String[] args) {
GridTest frame = new GridTest();
frame.setVisible(true);
}
public GridTest(){
setTitle("Grid Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setBounds(300, 100, 531, 483);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
scrollPane = new JScrollPane();
scrollPane.setBounds(0, 0, 526, 452);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
contentPane.add(scrollPane);
gridPane = new JPanel() {
public void paintComponent( Graphics g ){
super.paintComponent(g);
drawGrid(g);
g.dispose();
}};
Dimension gridPaneSize = new Dimension(28672,14336);
//Dimension gridPaneSize = new Dimension(4096,4096);
gridPane.setBackground(Color.BLACK);
gridPane.setPreferredSize(gridPaneSize);
scrollPane.setViewportView(gridPane);
}
public static void drawGrid(Graphics g)
{
int width = gridPane.getWidth();
int height = gridPane.getHeight();
g.setColor(Color.gray);
// draw horizontal long lines
for(int h = 0; h < height; h+=64){
g.drawLine(0, h, width, h);
}
// draw even grid vert lines
for(int w = 0; w < width; w+=64){
for(int h = 0; h < height; h+=128){
g.drawLine(w, h, w, h+64);
}
}
// draw odd grid vert lines
for(int w = 32; w < width; w+=64){
for(int h = 64; h < height; h+=128){
g.drawLine(w, h, w, h+64);
}
}
}
}
EDIT: The updated/fixed version of this code is below, in my answer to the question.
paint to the Objects, that are visible in the JViewport
in all cases you can to determine if(Rectangle(viewport.getViewRect()).intersects(Re ctangleFromDesiredObject))
returns true
, if yes then Objects
is visible in JViewport
for example
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;
public class TilePainter extends JPanel implements Scrollable {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Tiles");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(new TilePainter()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private final int TILE_SIZE = 50;
private final int TILE_COUNT = 100;
private final int visibleTiles = 10;
private final boolean[][] loaded;
private final boolean[][] loading;
private final Random random;
public TilePainter() {
setPreferredSize(new Dimension(TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
loaded = new boolean[TILE_COUNT][TILE_COUNT];
loading = new boolean[TILE_COUNT][TILE_COUNT];
random = new Random();
}
public boolean getTile(final int x, final int y) {
boolean canPaint = loaded[x][y];
if (!canPaint && !loading[x][y]) {
loading[x][y] = true;
Timer timer = new Timer(random.nextInt(500),
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
loaded[x][y] = true;
repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
});
timer.setRepeats(false);
timer.start();
}
return canPaint;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle clip = g.getClipBounds();
int startX = clip.x - (clip.x % TILE_SIZE);
int startY = clip.y - (clip.y % TILE_SIZE);
for (int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
for (int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
if (getTile(x / TILE_SIZE, y / TILE_SIZE)) {
g.setColor(Color.GREEN);
} else {
g.setColor(Color.RED);
}
g.fillRect(x, y, TILE_SIZE - 1, TILE_SIZE - 1);
}
}
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE * Math.max(1, visibleTiles - 1);
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE;
}
}
EDIT
great example about Rectagle.intersects(Rectagle)
by (HFOE
here) Encephalopathic
from old.sun.forum57
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class IsRectVisible {
private static void createAndShowUI() {
JFrame frame = new JFrame("IsRectVisible");
frame.getContentPane().add(new IsRectVisibleGui());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowUI();
}
});
}
}
class IsRectVisibleGui extends JPanel {
public static final Rectangle RECT = new Rectangle(250, 200, 100, 100);
public static final Dimension INNER_PANEL_SIZE = new Dimension(600, 800);
private static final Dimension SCROLLPANE_SIZE = new Dimension(250, 300);
private static final String NOT_VISIBLE = "Not Visible";
private static final String VISIBLE = "Visible";
private static final long serialVersionUID = 1L;
private InnerPanel innerPanel = new InnerPanel();
private JViewport viewport = new JViewport();
private JLabel statusLabel = new JLabel(NOT_VISIBLE);
IsRectVisibleGui() {
JScrollPane scrollpane = new JScrollPane();
scrollpane.setViewport(viewport);
viewport.add(innerPanel);
scrollpane.setPreferredSize(SCROLLPANE_SIZE);
viewport.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
Rectangle viewRect = viewport.getViewRect();
if (viewRect.intersects(RECT)) {
statusLabel.setText(VISIBLE);
} else {
statusLabel.setText(NOT_VISIBLE);
}
}
});
setLayout(new BorderLayout());
add(scrollpane, BorderLayout.CENTER);
add(statusLabel, BorderLayout.SOUTH);
}
class InnerPanel extends JPanel {
private static final long serialVersionUID = 1L;
InnerPanel() {
setPreferredSize(INNER_PANEL_SIZE);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.setStroke(new BasicStroke(4));
g2.draw(RECT);
}
}
}
Thanks to mKorbel's answer above, I was able to fix up the code, using getViewRect().
For anyone in the future, who may want to do something similar, I get a Rectangle of the viewport, then use for loops to check if the x/y (top corner) of the viewrect is within a tile (1024x1024) increment. If it is, then I draw the grid squares starting from the x/y increment tile to the width/height of the viewport +1 tile (1024). Scrolling from top to bottom in one quick swipe only uses about 5% CPU which is acceptable.
Here is the updated code for the JPanel scrollable grid:
import java.awt.*;
import javax.swing.*;
public class GridTest extends JFrame
{
private static final long serialVersionUID = 6632092242560855625L;
static JPanel gridPane;
static JViewport view;
public static void main(String[] args) {
GridTest frame = new GridTest();
frame.setVisible(true);
}
public GridTest(){
setTitle("Grid Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600,600);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane();
setContentPane(scrollPane);
view = scrollPane.getViewport();
gridPane = new JPanel() {
private static final long serialVersionUID = 2900962087641689502L;
public void paintComponent( Graphics g ){
super.paintComponent(g);
drawGrid(g, view.getViewRect());
}};
Dimension paneSize = new Dimension(28672,14336);
gridPane.setPreferredSize(paneSize);
gridPane.setBackground(Color.gray);
scrollPane.setViewportView(gridPane);
}
static void drawGrid(Graphics g, Rectangle view){
int wMax = gridPane.getWidth();
int hMax = gridPane.getHeight();
g.setColor(Color.black);
Rectangle tile = view;
// set corner tile x/y to the tile increment.
for(int w = 0; w < wMax; w+= 1024)
{
if(tile.x >= w && tile.x < w+1024) { tile.x = (w); }
for(int h = 0; h < hMax; h+= 1024)
{
if(tile.y >= h && tile.y < h+1024) { tile.y = (h); }
}
}
int xTop = tile.x;
int yTop = tile.y;
int width = (int) tile.getWidth();
int height = (int) tile.getHeight();
width = xTop + width;
height = yTop + height;
// Draw even grid squares within visible tiles, starting at top corner tile.
for(int w = xTop; w < width+1024; w+=64)
{
for(int h = yTop; h < height+1024; h+=128)
{
g.fillRect(w+1, h+1, 63, 63);
}
}
// Draw odd grid squares within visible tiles, starting at top corner tile.
for(int w = xTop-32; w < width+1024; w+=64)
{
for(int h = yTop+64; h < height+1024; h+=128)
{
g.fillRect(w+1, h+1, 63, 63);
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With