0

I was trying to understand the difference between JFrame and JPanel. I tend to use subclasses of JFrame instead of JPanel, but people always tell me that it's better to use a subclass of JPanel instead. Here is an example of me using JFrame:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;

public class Game extends JFrame implements Runnable {
int x, y, xCoord, yCoord;
private Image dbImage;
private Graphics dbg;

public void move() {
    x += xCoord;
    y += yCoord;
    if (x <= 20) {
        x = 20;
    }
    if (x >= 480) {
        x = 480;
    }
    if (y <= 40) {
        y = 40;
    }
    if (y >= 480) {
        y = 480;
    }
}

public void setXCoord(int xcoord) {
    xCoord = xcoord;
}

public void setYCoord(int ycoord) {
    yCoord = ycoord;
}

public class AL extends KeyAdapter {

    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == e.VK_LEFT) {
            setXCoord(-1);
        }
        if (keyCode == e.VK_RIGHT) {
            setXCoord(+1);
        }
        if (keyCode == e.VK_UP) {
            setYCoord(-1);
        }
        if (keyCode == e.VK_DOWN) {
            setYCoord(+1);
        }
        Game.this.repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == e.VK_LEFT) {
            setXCoord(0);
        }
        if (keyCode == e.VK_RIGHT) {
            setXCoord(0);
        }
        if (keyCode == e.VK_UP) {
            setYCoord(0);
        }
        if (keyCode == e.VK_DOWN) {
            setYCoord(0);
        }
        Game.this.repaint();

    }

}

public static void main(String[] args) {
    Game game = new Game();
    Thread t = new Thread(game);
    t.start();
}

public Game() {
    addKeyListener(new AL());
    setTitle("Game");
    setSize(500, 500);
    setResizable(true);
    setVisible(true);
    setBackground(Color.BLACK);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    x = 250;
    y = 250;

}

public void paintComponent(Graphics g) {
    g.setColor(Color.GREEN);
    g.fillOval(x, y, 15, 15);
}

@Override
public void paint(Graphics g) {
    dbImage = createImage(getWidth(), getHeight());
    dbg = dbImage.getGraphics();
    paintComponent(dbg);
    g.drawImage(dbImage, 0, 0, this);
}

@Override
public void run() {
    try {
        while (true) {
            move();
            Thread.sleep(30);
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

}

This works fine (except for there being a small delay when I hold down one of the buttons), but when I try to change my code by implementing JPanel instead of JFrame, nothing shows up... Here's the code for the JPanel subclass:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game extends JPanel implements Runnable {
int x, y, xCoord, yCoord;
private Image dbImage;
private Graphics dbg;
JFrame frame;

public void move() {
    x += xCoord;
    y += yCoord;
    if (x <= 20) {
        x = 20;
    }
    if (x >= 480) {
        x = 480;
    }
    if (y <= 40) {
        y = 40;
    }
    if (y >= 480) {
        y = 480;
    }
}

public void setXCoord(int xcoord) {
    xCoord = xcoord;
}

public void setYCoord(int ycoord) {
    yCoord = ycoord;
}

public class AL extends KeyAdapter {

    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == e.VK_LEFT) {
            setXCoord(-1);
        }
        if (keyCode == e.VK_RIGHT) {
            setXCoord(+1);
        }
        if (keyCode == e.VK_UP) {
            setYCoord(-1);
        }
        if (keyCode == e.VK_DOWN) {
            setYCoord(+1);
        }
        Game.this.repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == e.VK_LEFT) {
            setXCoord(0);
        }
        if (keyCode == e.VK_RIGHT) {
            setXCoord(0);
        }
        if (keyCode == e.VK_UP) {
            setYCoord(0);
        }
        if (keyCode == e.VK_DOWN) {
            setYCoord(0);
        }
        Game.this.repaint();

    }

}

public static void main(String[] args) {
    Game game = new Game();
    Thread t = new Thread(game);
    t.start();
}

public Game() {
    frame = new JFrame();
    frame.addKeyListener(new AL());
    frame.setTitle("Game");
    frame.setSize(500, 500);
    frame.setResizable(true);
    frame.setVisible(true);
    frame.setBackground(Color.BLACK);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    x = 250;
    y = 250;

}

@Override
public void paintComponent(Graphics g) {
    g.setColor(Color.GREEN);
    g.fillOval(x, y, 15, 15);
}

@Override
public void paint(Graphics g) {
    dbImage = createImage(getWidth(), getHeight());
    dbg = dbImage.getGraphics();
    paintComponent(dbg);
    g.drawImage(dbImage, 0, 0, this);
}

@Override
public void run() {
    try {
        while (true) {
            move();
            Thread.sleep(30);
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

}
Eames
  • 321
  • 2
  • 19
  • You forgot to add the panel in the second example : _frame.getContentPane().add(this);_ – Arnaud Jan 13 '16 at 15:26
  • http://stackoverflow.com/questions/13212431/jpanel-vs-jframe-in-java – chenchuk Jan 13 '16 at 15:29
  • @Berger Thanks, I didn't see that... But could you explain to me why you would have to add the jframe to the jframe if the jframe is the top level component? – Eames Jan 13 '16 at 18:50
  • No , _this_ is the _Game_ JPanel, since it is called from within the Game class . See the answer below for other explanations . – Arnaud Jan 13 '16 at 19:06
  • @Berger Sorry, what I meant to say was: why are you adding Game to the frame (which is essentially a JPanel) if JFrame is a top level component. Doesn't that mean that it should be something more along the lines of adding a JFrame to a JPanel instead of vice versa. – Eames Jan 13 '16 at 21:19

2 Answers2

1

As the code shows, you need a custom JPanel, because you want to change the behavior of some methods like paintComponent.

However, you don't need a custom JFrame, so no need to create a class extending it.

Finally, your main class has no need to be your panel class.

Here is an example class, I moved the frame stuff from Game's constructor to this main class.

 public class MainClass {

    public static void main(String[] args) {

    Game game = new Game();

    JFrame frame = new JFrame();

    frame.addKeyListener(new AL());
    frame.setTitle("Game");
    frame.setSize(500, 500);
    frame.setResizable(true);
    frame.getContentPane().add(game);
    frame.setBackground(Color.BLACK);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);

    Thread t = new Thread(game);
    t.start();

    }


    }
Arnaud
  • 17,229
  • 3
  • 31
  • 44
1

A JFrame is a far more complex component then you might think, for starters, it has a JRootPane as it's primary container, which contains the contentPane, JMenuBar and controls the glassPane

RootPane

see How to Use Root Panes for more details.

The JFrame also has decorations (borders), these borders are painted within the confines of the window itself, the contents are then laid out within these, so the borders don't paint over then.

When you override paint of a top level container, like JFrame, there are a number of issues which you run into:

  • It's not double buffered, which can cause flickering as the window is updated
  • The other components can be painted independently of the frame (so the frame's paint method is not called), which can cause no end of issues
  • You can now paint beneath the frame's decorations, see Java graphic image, How to get the EXACT middle of a screen, even when re-sized and How can I set in the midst? for more details.
  • You're locking your self into a single use case, you can't add windows to other containers, which reduces your components re-use value

Generally speaking, from a OOP point of view, you're not actually adding any new functionality to the class (or least none which can't be generated through better approaches).

When you use something like JPanel, all other the above are no longer of concern:

  • They are double buffered by default
  • If you use a layout manager (on the content pane), the component will be laid out within the frame's decorations
  • The width and height of the component represent the whole viewable area
  • You can add this component to what ever container you want

You should also override the JPanel's getPreferredSize method and return the preferred size you want your panel to generally be, then you can use JFrame#pack to "pack" the window around it, this will make the window larger then the content area, but means you're not scratching your head wondering why you set the window to a certain size, but your component is smaller

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366