Hello fellow programmers,
I've ran into a little issue in my code that I can't seem to crack. It has to do with the Jframe; Graphics area of Java. The code that I'll post below, is over a drawing method. Which purpose is to draw the "rooms" that are in a ArrayList roomList which is located in another class hence lvl. before. This off-course doesn't happen, hence the post on here.
public class LevelGUI implements Observer {
private Level lv;
private Display d;
public LevelGUI(Level level, String name) {
this.lv = level;
JFrame frame = new JFrame(name);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
d = new Display(lv, 500, 500);
frame.getContentPane().add(d);
frame.pack();
frame.setLocation(0, 0);
frame.setVisible(true);
}
private class Display extends JPanel {
public Display(Level fp, int x, int y) {
addKeyListener(new Listener());
setBackground(Color.GRAY);
setPreferredSize(new Dimension(x + 20, y + 20));
setFocusable(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
private void draw(Graphics g) {
Level lvl = new Level();
for(int i = 0; i < lvl.roomList.size(); i++) {
Room room = lvl.roomList.get(i);
g.setColor(room.floorColor);
g.drawRect(room.posX, room.posY, room.roomWidth, room.roomHeight);
}
}
}
}
To get some background info on the program. roomList is the ArrayList, and it is filled with various different sized and colored rooms. The rooms themselves are objects.
Here comes first Level class:
public class Level extends Observable {
private boolean Switch = true;
public ArrayList<Room> roomList = new ArrayList<Room>();
(...)
}
Here is the Class Room() that is used to create the rooms.
public class Room {
Color floorColor;
int roomWidth;
int roomHeight;
int posX;
int posY;
public Room(int dx, int dy, Color color) {
this.floorColor = color;
this.roomHeight = dy;
this.roomWidth = dx;
this.posY = 0;
this.posX = 0;
}
(...)
}
I've managed to locate where the problem is thought to occur, and it's the code in the for-loop. I tried switching the roomList.size() for an integer to test if it was the loop., But it wasn't. It is possible to draw a figure outside of the for-loop.
and again, the problem isn't an error message, the program simply doesn't draw the rooms that I've instructed it to draw in the method draw().
The display output looks like this:
Thanks beforehand!
Be aware that the paintComponent() method is invoked by Swing whenever the framework thinks the component needs to be rendered on screen. This usually is when the window is getting visible - initially or because some other window no longer hides the component. Such events are out of your control.
So your application should create a state and be ready to draw it anytime. Therefore you do not create state (like a level) inside the paint() or paintComponent() method. Put that elsewhere - if need be into the constructor.
Looking at you code:
As you are creating a new level inside paintComponent()/draw(), is it correct to assume that this level has no rooms associated? In that case the method is right to return without having painted anything.
If your application thinks the screen should be updated call repaint(), knowing that the paint() method will be called by the framework soon.
I am making a program that takes a two dimensional integer array and uses its data to draw tiles to the screen in the arrangement specified in the array. Without modifying any of the code, the program will execute fine about 4 out of 5 times. Other times the custom JPanel will not display anything. After inserting system.out.print() in various places I have determined that it is caused by the paintComponent method not being called when nothing is displayed. Obviously it is called when the tiles are displayed perfectly. I can't seem to find the source of this inconsistency. Why would it work the majority of the time and not every once in a while?
Its called Isopanel because it will eventually display tiles in an isometric formation.
0s equate to water tiles and 1s equate to sand tiles.
JPanel Class
public class IsoPanel extends JPanel
{
private ArrayList <BufferedImage> tiles;
private int[][] leveldata =
{
{0,0,0,0,0,0,0,0,0,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,0,0,0,0,0,0,0,0,0}
};
public IsoPanel()
{
tiles = new ArrayList<BufferedImage>();
tiles.add(Frame.loadImage("water.png"));
tiles.add(Frame.loadImage("sand.png"));
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
for (int i=0; i<10; i++)
{
for (int j=0; j<10; j++)
{
int x = j * 50;
int y = i * 50;
int tileType = leveldata[i][j];
placeTile(tileType, x, y, g);
}
}
}
public void placeTile (int tile,int x,int y, Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(tiles.get(tile), null, x, y);
}
}
and JFrame class:
public class Frame extends JFrame
{
public Frame()
{
super ("Iso");
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setBounds(0,0,screenSize.width, screenSize.height);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
BorderLayout bord = new BorderLayout();
setLayout(bord);
IsoPanel iso = new IsoPanel();
add(iso,BorderLayout.CENTER);
GridLayout grid = new GridLayout(1,1);
iso.setLayout(grid);
iso.setVisible(true);
}
public static BufferedImage loadImage(String filename)
{
{
try
{
return ImageIO.read(new File(System.getProperty( "user.dir" )+"/src/"+filename));
}
catch(IOException e)
{
}
}
return null;
}
public static void main(String[] args)
{
Frame one = new Frame();
}
}
The main issue is the fact that you are calling setVisible on your frame before you've finished initialising the child components. This is a known issue with how frame prepares it's state...
So, instead of...
public Frame()
{
/*...*/
setVisible(true);
/*...*/
add(iso,BorderLayout.CENTER);
}
Try...
public Frame()
{
/*...*/
add(iso,BorderLayout.CENTER);
/*...*/
setVisible(true);
}
Additional...
You shouldn't be throwing away your exceptions. At the very least, you should be printing the exception or logging it.
You should be using an ImageObsever when drawing images. Instead of g2.drawImage(tiles.get(tile), null, x, y);, you should try using g2.drawImage(tiles.get(tile), x, y, this);. Images aren't always in state to be rendered immediately, this provides a means for the component to react to changes in the image state and automatically repaint themselves...
Even though you are setting the size of the parent frame, your IsoPanel component should be providing layout hints in the form of overriding getPreferredSize, allowing you to simply pack the main window. This discounts the possibility of different frame border sizes on different platforms and look and feel settings.
You may wish to take a look at Initial Threads and the important of using EventQueue.invokeLater to launch your UI
System.getProperty( "user.dir" )+"/src/"+filename) looks like it should be referencing an embedded resource...
I currently have a set of TickerSymbol() objects that scroll across the screen in my Swing JFrame. I'm not sure how to get it to wraparound though .. in terms of logic.
protected void animate() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setFont(BG_STRING_FONT);
g.setColor(Color.LIGHT_GRAY);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
List<TickerSymbol> tickers = ticker.getTickers();
synchronized(tickers){
for(TickerSymbol ts : tickers) {
//Create formatted string with ticker info
// This part works fine
...
}
}
}
private class TimerListener implements ActionListener {
public void actionPerformed(java.awt.event.ActionEvent e) {
//TODO need to make the ticker wrap around rather than hard coding.
if (imgX <= -2000) {
imgX = getWidth();
} else {
imgX -= 1;
}
repaint();
};
}
As you can see above, I've currently hardcoded the position at which 'imgX' resets to getWidth(). This means that the String will reset its position to the right corner of the application.
I've tried using "Iterables.cycle" from the Guava project but it ends up causing a memory overflow.
Iterable<TickerSymbol> live = Iterables.cycle(tickers);
while(live.iterator().hasNext()) {
....
Any help is appreciated!
Check out the Marquee Panel. It allows you to scroll a panel containing components. So you could create a panel with one JLabel containing the text you want to scroll, or you could add multiple labels to the panel which allows you to dynamically update the labels as the marquee is scrolling.
I'm making a game and although I'm mainly working on the logic first, I have a simple, fast-made draw method:
public class Draw extends JPanel {
private static final long serialVersionUID = 1L;
// BufferedImage heroitext;
Hero hero;
Vector<Enemy> enemies;
Vector<Bullet> ammo;
Vector<Terrain> terrains;
public Draw() {
// (int)try {
// heroitext = ImageIO.read(getClass().getResource("/images/circulo.png"));
// } catch (IOException e) {
// e.printStackTrace();
// }
}
public void render(Level lvl) {
hero = lvl.getHero();
ammo = lvl.getAmmo();
enemies = lvl.getEnemies();
terrains = lvl.getTerrains();
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("drawing");
//draw Hero
g.drawRect((int)hero.getX(), (int)hero.getY(), hero.getWidth(), hero.getHeight());
//draw Enemies
for(int i=0; i<enemies.size(); i++)
{
if(!enemies.get(i).isToKill()) g.drawRect((int)enemies.get(i).getX(), (int)enemies.get(i).getY(), enemies.get(i).getWidth(), enemies.get(i).getHeight());
for(int j=0; j<enemies.get(i).getAmmo().size(); j++)
{
g.drawRect((int) enemies.get(i).getAmmo().get(j).getX(), (int)enemies.get(i).getAmmo().get(j).getY(), enemies.get(i).getAmmo().get(j).getWidth(), enemies.get(i).getAmmo().get(j).getHeight());
}
}
//draw Ammo
for(int i=0; i<ammo.size(); i++)
{
g.drawRect((int)ammo.get(i).getX(), (int)ammo.get(i).getY(), ammo.get(i).getWidth(), ammo.get(i).getHeight());
}
//draw Terrains
for(int i=0; i<terrains.size(); i++)
{
g.drawRect((int)terrains.get(i).getX(), (int)terrains.get(i).getY(), terrains.get(i).getWidth(), terrains.get(i).getHeight());
}
}
}
And it usually works great, but sometimes, even when I don't change code, some Runs give me a white screen, and with a few println's I found out that although the code reaches repaint() the paintComponent method isn't called at all.
As I said, this only happens sometimes and IT SEEMS unpredictable.
Another thing that happens is when I add around 50000 enemies to the enemy vector, the paintComponent is never called, no matter how many runs I try. From what I researched, some people say it's because java won't bother drawing a panel if another panel is already waiting to be drawn (or something like that) but that doesn't make sense on the first case since I do not change any code.
Can someone explain me what is actually happening and how to properly fix it?
I have a custom, abstract class 'Panel' which extends JPanel. There aren't many differences with the two when painting. I have a Panel and I'm simulating an animation by updating the x value of an image. I have two animations right now, one that properly repaints and another than does not. This is for the one that does not. The one that works will be labelled A, the one that doesn't will be B.
A and B follow the same format. Update some variable on the Panel, calls update (a method in Panel which calls PaintComponent) and then calls repaint. It calls repaint after because this issue was with A before and was solved that way.
A: Updates an image variable.
B: Updates the x variable of an image.
The Problem: The repaint doesn't clear the old image location and so it's a choppy mess across the screen.
What I've tried:
I've seen the super.PaintComponent(g) mentioned a lot, but this
hasn't solved the problem.
I've tried changing the order for when the repaint/update methods are
called.
Repaint does not update the Panel at all. (Probably because the
painting is done in PaintComponent)
Any help would be appreciated.
Code:
Panel:
public Panel (boolean visible){
super();
this.setLayout(new BorderLayout(640, 416));//sets the Layout type of the panel
this.setOpaque(false);//Makes it so that the panel underneath can be seen where images aren't drawn
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
}
public void paintComponent (Graphics g){
setUp();
drawOff();
setDown(g);
}
private void setUp(){
off_screen = gc.createCompatibleImage(getSize().width, getSize().height, Transparency.TRANSLUCENT);
buffer = off_screen.createGraphics();
}
protected abstract void drawOff();
private void setDown(Graphics g){
g.drawImage(off_screen,0,0,this);
off_screen.flush();
}
public void update(){
paintComponent(this.getGraphics());
}
Animation Methods (mg is the panel in question):
private void battleStart(User user) {
for (int i = 0; i < user.battle.length; i++) {
mg.battleStart(user.battleStart(i));
mg.update();
try {
Thread.sleep(150);
} catch (Exception e) {
}
mg.repaint();
}
}
private void animateStart(User user){
for (int i = 0; i < 10; i++){
mg.x = mg.x + 10;
mg.update();
try {
Thread.sleep(100);
} catch (Exception e) {
}
mg.repaint();
}
}
I think your design is way off and that is why things are not working. I'm not quite sure how your non-abstract JPanels work, but consider making your parent JPanel something more along these lines:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyPanel extends JPanel {
private GraphicsEnvironment ge;
private GraphicsDevice gs;
private GraphicsConfiguration gc;
private BufferedImage offScreen;
public MyPanel(boolean visible) {
super();
this.setLayout(new BorderLayout(640, 416)); // strange constants for this layout.
this.setOpaque(false);
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
setUp();
}
});
}
#Override
// don't make this public. Keep it protected like the super's
// just draw in this method. Don't call other methods that create buffers
// or draw to buffers.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (offScreen != null) {
g.drawImage(offScreen, 0, 0, null);
}
}
private void setUp() {
offScreen = gc.createCompatibleImage(getSize().width, getSize().height,
Transparency.TRANSLUCENT);
}
// draw to the buffer outside of the paintComponent
// and then call repaint() when done
public void upDateOffScreen() {
// ?? offScreen.flush(); // I've never used this before,
// so am not sure if you need this here
Graphics2D osGraphics = offScreen.createGraphics();
// TODO: do drawing with osGraphics object here
osGraphics.dispose();
repaint();
}
}
Also and again,
Do all long processing methods off of the EDT (Event Dispatch Thread).
Never call Thread.sleep(...) on the EDT.
Consider using Swing Timers instead of using Thread.sleep for the animations.
It's OK to call repaint on your JPanel off of the EDT, but for the most part that's about it.
All other Swing methods should be called on the EDT.
Read, re-read, and study the 2D and Swing graphics tutorials.