Reputation: 85
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
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
Reputation: 109815
1.don't use null layout for JScrollPan
e, 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
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
Reputation: 988
Are you sure about this?
public void paintComponents(Graphics g)
Do you mean instead
public void paintComponent(Graphics g)
Upvotes: 5