Repainting a JPanel in a JScrollPane

bluesawdust picture bluesawdust · Aug 8, 2012 · Viewed 7.4k times · Source

The project that I am working on is an inter-computer utility for playing table top games, and a key part of that is an interactive grid/map. Well I have built in functionality for custom map creation, and want the screen to be able to display very large maps, as well as very small maps. To this effect I have created a series of classes with which I can display a grid of any size. However what I would like to do is to put the JPanel which this is accomplished with into a JScrollPane, thereby making infinite space for a map. When I try to do this the map that displays perfectly out of a JScrollPane does not paint at all. I suspect that it has to do with the Graphics context but have thus far been unable to find the solution.

In the following code, remark the scroll lines and switch the grid.draw method calls in the initialization to see what I would like it to look like.

The full code is as follows:

package pac;

import javax.swing.*;
import javax.swing.text.*;

import java.awt.*;

public class MainScreen extends JFrame
{
int width, height, x, y;
Grid grid;

public static void main(String[] args) 
{
    MainScreen mainScreen = new MainScreen();
    mainScreen.setVisible(true);        
}

public MainScreen()
{
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    float factor = 1.25f;
    width = (int)(dim.width / factor);
    height = (int)(dim.height / factor);
    x = (int)(dim.width / (factor * 8));
    y = (int)(dim.height / (factor * 8));

    setBounds(x,y,width,height);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setTitle("DnD Main Screen");
    setVisible(true);
    grid = new Grid();

    JScrollPane scrollPane = new JScrollPane(grid);
    scrollPane.setBounds(212,0,650,500);
    scrollPane.setViewportView(grid);
    add(scrollPane);
    this.setLayout(null);

    Thread draw = new Thread()
    {
        public void run()
        {
            while(true)
            {
                Graphics g = getGraphics();

                grid.repaint();
                //grid.paintComponents(getGraphics());

                g.setColor(Color.blue);
                g.fillRect(0 + 8, 0 + 30, 212, 200);
                g.fillRect(863 + 8, 0 + 30, 212, 200);

                g.setColor(Color.red);
                g.fillRect(0 + 8, 200 + 30, 212, 375);
                g.fillRect(863 + 8, 200 + 30, 212, 375);

                try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
                Thread.yield();
            }
        }
    };
    draw.start();
}

public class Grid extends JPanel
{
    Tile[][] tiles;
    public int x, y, across, down;
    public int topBar = 30;
    public int skinnyBar = 8;
    public Grid()
    {
        this.setPreferredSize(new Dimension(600,600));

        x=212 + skinnyBar;
        y=0+topBar;
        across = 13;
        down = 9;
        tiles = new Tile[across][down];
        for(int i = 0; i<across; i++)
            for(int j = 0; j<down; j++)
            {
                tiles[i][j] = new Tile(x+(i*50),y+(j*50),50);
            }
        this.setVisible(true);
    }
    public void paintComponents(Graphics g)
    {
        //super.paintComponents(g);
        draw(g);
    }
    public void draw(Graphics g)
    {
        for(int i = 0; i<across; i++)
            for(int j = 0; j<down; j++)
            {
                g.setColor(Color.black);
                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);
            }
    }
    private class Tile
    {
        int x, y, side;
        public Tile(int inX, int inY, int inSide)
        {
            x=inX;
            y=inY;
            side=inSide;
        }
    }
}

}

I have been working on this a while and have not been able to find and problems similar enough to mine to find the fix. Any advice? Please and thank you.

Answer

mKorbel picture mKorbel · Aug 8, 2012

1.don't use null layout for JScrollPane, use some of Standard LayoutManager,

2.don't use getGraphics(), this method is for printing to the printer or for save GUI as snapshot to the image of file

3.there no reason to use draw(), to prepare all Objects before and painting everything inside paintComponent()

4.most of examples are too old, based on Thread, don't use Thread, use Swing Timer instead

5.easiest way could

JFrame -> JScrollPane -> JPanel -> by using GridLayout to put there required amount of JPanels with desired Color, put these JPanels to the some type of array and by using Swing Timer to pick up JPanel from array and to change its Backgroung

example, have to add Swing Timer for paiting on period

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;
    }
}