bluesawdust
bluesawdust

Reputation: 85

Repainting a JPanel in a JScrollPane

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.

Upvotes: 4

Views: 7723

Answers (4)

David Kroukamp
David Kroukamp

Reputation: 36423

Hmm did my own little example which demonstrates repainting a JPanel which is wrapped in a JScrollPane:

The panel has a custom method setScrollPane(JSCrollPane jsp) which will be called after the panel and JScrollPane have been initialized this will allow the JPanel to call repaint() on the scrollPanes instance:

PanelPaintTest.java:

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class PanelPaintTest extends JFrame {

    public PanelPaintTest() {
        createUI();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new PanelPaintTest().setVisible(true);
            }
        });
    }

    private void createUI() {
        //create frame
        setTitle("JPanel and JScrollPane Paint Test");
        setSize(500, 500);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //create custom panel
        Panel panel = new Panel(500, 500);    
        //create scrollpane to hold JPanel
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(panel);    
        panel.setScrollPane(scrollPane);
        //custom method to set the panels JScrollPane which will be repainted when ever the panel is    
        getContentPane().add(scrollPane);//add scrollpane to the frame
    }
}

Panel.java

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.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;

class Panel extends JPanel {

    private int width, height, across, down, x = 0, y = 0;
    ;
    private Panel.Tile[][] tiles;
    private Color color = Color.black;
    private JScrollPane scrollPane;

    public Panel(int width, int height) {
        this.setPreferredSize(new Dimension(width * 2, height * 2));//simple to make the scrollbars visible

        this.width = width * 2;
        this.height = height * 2;

        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() {
        scrollPane.repaint();
    }

    void setScrollPane(JScrollPane scrollPane) {
        this.scrollPane = scrollPane;
    }

    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(x + (i * 50), y + (j * 50), 50);//took out x and y values didnt know what 
            }
        }
    }

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

The panel will draw the tiles you showed and they will flicker red or black every 2seconds.

HTH

Upvotes: 4

mKorbel
mKorbel

Reputation: 109815

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

Upvotes: 7

MadProgrammer
MadProgrammer

Reputation: 347184

Apart from NEVER performing updates to the UI outside the Event Dispatching Thread, I'd also suggest the following:

Make sure that the Grid pane calls super.paintComponent(...) this is important and you really, really, REALLY have to have a good reason not to.

If you want the panel to be transparent, use setOpaque(false) instead.

I'd also suggest you familiarize yourself with the Graphics.drawLine method. I think it will be more efficient

You have to remember, you do not have control over the paint process. Just accept it. You can provide requests to the graphics pipeline that you want to update (invalidate, revalidate, repaint), but that's about it. The rest is up to the OS and the repaint manager to decide.

this.setLayout(null); is a really, really bad idea, it might just be my opinion, but this really is a bad idea. If nothing else get to know GridBagLayout or use compund panels and layout managers, it will just save you a lot of headaches

Upvotes: 4

Leon
Leon

Reputation: 988

Are you sure about this?

public void paintComponents(Graphics g)

Do you mean instead

public void paintComponent(Graphics g)

Upvotes: 5

Related Questions