Im currently trying to make my first game and i am having trouble getting the repaint() method to work. I have checked my keyListeners and have confirmed that they are working a ok! The ship that i have created moves but only if i forcibly resize the window by dragging on the sides. If anyone has any tips i would be very greatful!
If you need any more information feel free to ask!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Main extends Canvas implements KeyListener{
public static Ship playerShip = new Ship(150,450,"F5S4-Recovered.png");
public int numberOfEnemies = 0;
public static void createFrame(){
Window frame1 = new Window();
final JPanel pane = new JPanel(){
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, 1000, 1000);
g.drawImage(playerShip.image, playerShip.xPos1, playerShip.yPos1, this);
}
};
frame1.add(pane);
}
public void keyTyped(KeyEvent e){System.out.println("KeyTyped");}
public void keyPressed(KeyEvent e){
switch(e.getKeyCode())
{
case KeyEvent.VK_LEFT :
playerShip.xPos1-=2;
break;
case KeyEvent.VK_RIGHT:
playerShip.xPos1+=2;
break;
}
repaint();
}
public void keyReleased(KeyEvent e){}
public static void main(String args[]){
createFrame();
}
}
Window class ----------------------------------------------------------
import javax.swing.*;
public class Window extends JFrame{
public Window()
{
setTitle("Space Game");
setSize(800,800);
setDefaultCloseOperation(EXIT_ON_CLOSE);
addKeyListener(new Main());
setVisible(true);
}
}
Your call to repaint() is repainting the class Canvas, but the painting is done on the JPanel pane. The resizing causes an automatic repaint of the panel. So to fix you want to pane.repaint(), but you can't do that unless you put the panel as a class member, so you can access it from the listener method. Right now, it's currently locally scoped in the createFrame() method.
Also, you should probably add the listener to the panel instead, and not even extend Canvas, since you're not even using it
Other Notes:
Look into using Key Bindings instead of low-level KeyListener
Swing apps should be run from the Event Dispatch Thread (EDT). You can do so by simply wrapping the code in your main in a SwingUtilities.invokeLate(..). See more at Initial Threads
Again, I'll just add, Don't extends Canvas
Related
This code displays x and y points based on where you click. I want to add a JPanel so that the frame does not start measuring from the title bar which is unreachable to the user.
Every time I switch from Frame to JFrame I get this:
example
The previous click does not disappear... What is the easiest way to fix this or am i able to add a JPanel on a Frame?
Below is my code:
************************************************************/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.Color;
public class Proj07Runner {
Proj07Runner(){
System.out.println(
"Terminal text");
GUI gui = new GUI(); //instantiate a GUI
}//end main
}
**Here is where switching from Frame to JFrame causes the change**
class MyFrame extends JFrame{
int clickX;
int clickY;
public void paint(Graphics g){
g.setColor(Color.BLACK);
g.drawString(
"" + clickX + ", " + clickY, clickX, clickY);
}//end paint()
}
class GUI {
public GUI(){//constructor
//Create a new JFrame object, set size, title, etc.
MyFrame displayWindow = new MyFrame();
JPanel myPanel = new JPanel();
displayWindow.setSize(300,100);
displayWindow.setTitle("Title");
myPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
displayWindow.setVisible(true);
//Instantiate and register an anonymous Listener;
// object which will terminate the program when the;
// user closes the JFrame.
displayWindow.addWindowListener(new WProc1());
myPanel.addMouseListener(
new MouseProc(displayWindow));
displayWindow.addMouseListener(
new MouseProc(displayWindow));
}
}
//This listener class monitors for mouse presses and;
// displays the coordinates of the mouse pointer when the
// mouse is pressed on the source object. Note that this
// class extends is an adapter class.
class MouseProc extends MouseAdapter{
MyFrame refToWin; //save a reference to the source here
MouseProc(MyFrame inWin){//constructor
refToWin = inWin;//save ref to window
}//end constructor
//Override the mousePressed method to determine and;
// display the coordinates when the mouse is pressed.
public void mousePressed(MouseEvent e){
//Get X and Y coordinates of mouse pointer and store
// in an instance variable of the JFrame object
refToWin.clickX = e.getX();
refToWin.clickY = e.getY();
//refToWin.clearRect(0,0,300,100);
// Force the JFrame object to be repainted in order to
// display the coordinate information.
refToWin.repaint();
}//end mousePressed()
}
//The following listener is used to terminate the program
// when the user closes the frame. Note that this class
// extends an adapter class.
class WProc1 extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
The coordinates look awkward because the MouseListener is on the Frame, not the Panel. The coordinates will always be in the component's coordinate system that the listener is attached to.
So if you do the rendering in the JPanel, why not attach the MouseListener to that JPanel?
While AWT is a heavy weight UI framework (every AWT component has some native UI representation such as X Widget or a Windows 'window'), Swing is a lightweight framework. Only the root component JWindow, JFrame or JDialog have native representations, the rest is within the JVM. When mixing components you need to know about this difference.
See also https://en.wikipedia.org/wiki/Abstract_Window_Toolkit#Mixing_AWT_and_Swing_components
Ummmm, I just learned that the problem I was referring to may have been fixed since Java 6...
Somehow I felt like creating a small demo using Swing only:
package com.mycompany.test;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestPanel extends JPanel {
private Point clickPosition;
public TestPanel() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
clickPosition = e.getPoint();
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (clickPosition != null) {
g.setColor(Color.BLACK);
g.drawLine(clickPosition.x-5, clickPosition.y, clickPosition.x+5, clickPosition.y);
g.drawLine(clickPosition.x, clickPosition.y-5, clickPosition.x, clickPosition.y+5);
g.drawString(String.format("(%d, %d)", clickPosition.x, clickPosition.y), clickPosition.x+5, clickPosition.y-5);
}
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TestPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
I am writing this code so that it will add JPanels and will change the background color when clicked. The code in the class that runs the JFrame class is:
import javax.swing.*;
public class project9Driver {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Project9 w=new Project9();
w.setVisible(true);
w.setSize(900, 900);
}
}
The Project9 class is:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class Project9 extends JFrame implements MouseListener{
JPanel panes[]=new JPanel[64];
public void Project9(){
JFrame mainFrame=new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container mainBox=mainFrame.getContentPane();
mainBox.setLayout(new GridLayout(8,8));
mainBox.addMouseListener(this);
for(int i=0;i<=63;i++){
panes[i].setBackground(Color.WHITE);
mainBox.add(panes[i]);
}
}
public void paint(Graphics g){
super.paintComponents(g);
}
public void mouseClicked(MouseEvent e) {
for(int i=0;i<=64;i++){
if(e.getSource()==panes[i]){
Random xR=new Random();
Random yR=new Random();
Random zR=new Random();
int x=xR.nextInt(255),y=yR.nextInt(255),z=zR.nextInt(255);
panes[i].setBackground(new Color(x,y,z));
}
}
}
}
Whenever I try to run the program, it comes up with an empty GUI window. What am I missing?
Project9 is a Frame, and you're creating another Frame inside of Project9 and you're not showing it, and becouse of that, just Project9 (w) is draw on screen, but it doesn't have anything.
You have to use "this" instead of another frame.
public class Project9 extends JFrame implements MouseListener{
JPanel panes[]=new JPanel[64];
public void Project9(){
//JFrame mainFrame=new JFrame(); delete this.
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container mainBox= this.getContentPane();
Three answer all point out different problems with your code.
You should start with a proper working example. The TopLevelDemo.java code from the Swing tutorial on Using Top Level Containers will show you the basics and the proper way to create GUI components.
You should not even be extending JFrame. It is important that all components are created on the Event Dispatch Thread which is why the invokeLater() code is used in the tutorial.
Use the tutorial for examples of using all Swing components. Other example will show you how to extend JPanel for a more complex GUI. You don't need to extend JFrame.
The original problem I noticed with the code is:
public void paint(Graphics g){
super.paintComponents(g);
}
Don't override the paint() method. There is no reason to do this!
The paint() method is for painting and you are not doing any custom painting.
First problem is this that you are using constructor Project9 w=new Project9();
and in class Project9 you are defining method public void Project9(){ you should remove void keyword.
public Project9(){
//JFrame mainFrame=new JFrame(); delete this.
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container mainBox= this.getContentPane();
I'm creating a simple Break Out style game. The main game extends JFrame and I'm adding a JPanel to the frame.
When I was using paint() to draw the game graphics the items sat within the window as expected (i.e by their x, y coordinates).
I've updated the code to use BufferStrategy as I was getting flickering. Since the, the graphics that are rendered are offset by 22px.
This means the Bricks are off the top of the screen!
The code is as follows:
package BreakOut;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
public class Game extends JPanel implements KeyListener{
GameStateManager gsm = new GameStateManager();
BufferStrategy strategy;
public Game() {
//add menu state to GameStateManager
gsm.add(new MenuState(gsm));
createFrame();
while(true)
{
gsm.update();
//repaint();
render();
try{
Thread.sleep(10);
}
catch(InterruptedException e)
{
}
}
}
public void createFrame()
{
JFrame frame = new JFrame("Mini Tennis");
frame.setLayout(new BorderLayout());
this.setPreferredSize(new Dimension(400,400));
frame.add(this);
frame.pack();
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);
this.setFocusable(true);
frame.createBufferStrategy(2);
strategy = frame.getBufferStrategy();
frame.setVisible(true);
System.out.println(frame.getInsets());
}
public void render()
{
Graphics g = strategy.getDrawGraphics();
super.paint(g);
gsm.render(g);
g.dispose();
strategy.show();
}
public void keyPressed(KeyEvent k) {
switch(gsm.getState())
{
case MAIN_MENU:
if(k.getKeyCode()==KeyEvent.VK_ENTER)
{
//add the PlayState to the Stack and update enum value
gsm.add(new PlayState(gsm, this));
}
break;
case PLAYING:
if(k.getKeyCode()==KeyEvent.VK_P)
{
//add the PlayState to the Stack and update enum value
gsm.add(new PauseState(gsm));
}
break;
case PAUSE:
if(k.getKeyCode()==KeyEvent.VK_P)
{
gsm.pop();
}
break;
case GAME_OVER:
if(k.getKeyCode()==KeyEvent.VK_ENTER)
{
gsm.pop();
}
break;
}
//send input to GameStateManager
gsm.keyPressed(k.getKeyCode());
}
public void keyReleased(KeyEvent k) {
gsm.keyReleased(k.getKeyCode());
}
public void keyTyped(KeyEvent k) {
gsm.keyTyped(k.getKeyCode());
}
public static void main(String[] args) {
new Game();
}
}
When I output System.out.println(frame.getInsets()); I get
java.awt.Insets[top=22,left=0,bottom=0,right=0]
I'm obviously doing something wrong but can't figure out why adding BufferStrategy would create the JPanel to be offset by 22px
Any help would be appreciated :)
Frames have borders and decorations, which is included within the bounds of the frame (they don't get added to the outside), from the looks of things you're using MacOS and the 22pixels to the top is the window title.
Best solution is, don't use the frame as the render surface, instead, use the Game class. This will mean it will need to extend from java.awt.Canvas instead of javax.swing.JPanel and you will need to create the BufferStrategy from it
If you override the Canvas's getPreferredSize method, you can use pack on the frame it will pack the window around this, so the physical frame will be larger then the content, but the content will be the size you would prefer
You will also want to move you main/game loop to separate thread, as this is current in the risk of blocking the Event Dispatching Thread, which could cause you no end of issues (like never getting a key event)
I have recently been making a game and came across a problem I could not solve. My problem is with removing the content pane of a JFrame and setting as something else. While this works, the KeyListener in the class of the content pane does not work unless I change the primary window on the computer to something else then back to the JFrame.
I replicated the problem in a smaller amount of code than what is was originally:
import java.awt.*;
import java.awt.event.*;
import javax.awt.swing.*;
public class TheFrame extends JFrame{
private JButton play;
private FirstPanel fp;
private SecondPanel sp;
public TheFrame(){
setSize(800, 600);
setLocationRelativeTo(null);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
fp = new FirstPanel();
setContentPane(fp);
setVisible(true);
}
public static void main(String args[]){
TheFrame tf = new TheFrame();
}
class FirstPanel() extends JPanel{
private boolean test = false;
public FirstPanel(){
play = new JButton("play");
play.addActionListener(new PlayListener());
add(play);
}
public void paintComponent(Graphics g){
if(test == true){
sp = new SecondPanel();
removeAll();
revalidate();
setContentPane(sp);
}
}
class PlayListener implements ActionListener{
public void actionPerformed(ActionEvent e){
test = true;
repaint();
}
}
}
}
Here is also the code for the class SecondPanel:
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
public class SecondPanel extends JPanel implements KeyListener{
private int draw = 0;
public SecondPanel(){
addKeyListener(this);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawString("press f to draw circles", 90, 40);
if(draw > 0){
for(int i = 0; i < draw; i++){
g.drawOval((i*100)+100, (i*100)+100, 100, 100);
}
}
}
public void keyTyped(KeyEvent e){
if(e.getKeyChar() == 'f' || e.getKeyChar() == 'F'){
draw++;
repaint();
}
}
}
So before anything else, this...
public void paintComponent(Graphics g){
if(test == true){
sp = new SecondPanel();
removeAll();
revalidate();
setContentPane(sp);
}
}
This incredibly bad! First, you are breaking the paint chain (not calling super.paintComponent) and secondly, you are changing the state of the component from within the paint cycle, this will trigger a new repaint request and will call your paintComponent again and again and again and again ....
Painting is for painting the current state of the component, nothing more. NEVER change the state of any component from within ANY paint method EVER
Instead of trying to use remove/add, consider using a CardLayout instead, see How to Use CardLayout. This will allow you to switch between the first and second panels based on your needs, from a centralised control point.
KeyListener is a fickle mistress, it wants all the attention, all of the time. It will only raise key events if the component it is registered to is focusable AND has focus. A better solution is to use the key bindings API, which has been designed to overcome this limitation and provide you with a level of control over the level of focus required to trigger the associated actions.
See How to Use Key Bindings for more details
To swap content of a container, be it a JFrame's contentPane or any JPanel, consider using a CardLayout since this tool was built specifically for this job.
Note that this code:
sp = new SecondPanel();
removeAll();
revalidate();
setContentPane(sp);
should never be found inside of a paintComponent method. This method is not under our direct control, and should be for painting and painting only. Also by not calling the super's method, you have broken the painting chain.
Also, instead of KeyListeners, use Key Bindings, and your functionality should work.
For instance, please have a look at the similar CardLayout code I created today for another similar question.
I am trying to write a simple 2d animation engine in Java for visualizing later programming projects. However, I am having problems with the window refresh. On running, the frame will sometimes display a blank panel instead of the desired image. This begins with a few frames at a time at apparently random intervals, worsening as the program continues to run until the actual image only occasionally blinks into view. The code for processing each frame is run, but nothing in the frame is actually displayed. I believe the problem may come from my computer more than my code (certainly not from bad specs though), but am not sure. Help much appreciated.
Three classes. Code here:
package animator;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.applet.AudioClip;
public class APanel extends JPanel
{
public APanel(int l, int h){
setPreferredSize(new Dimension(l,h));
setLocation(80, 80);
setVisible(true);
setFocusable(true);
}
public Graphics renderFrame(Graphics g){
return g;
}
public void paintComponent(Graphics g) {
requestFocusInWindow();
renderFrame(g);
}
}
package animator;
import java.awt.*;
public class Animator extends APanel
//extending the APanel class allows you to code for different animations
//while leaving the basic functional animator untouched
{
public static final int SCREEN_X = 700;
public static final int SCREEN_Y = 700;
int frameNum;
public Animator() {
super(SCREEN_X, SCREEN_Y);
frameNum = 0;
}
public Graphics renderFrame(Graphics g) {
frameNum++;
g.drawString(""+frameNum,5,12);
return g;
}
}
package animator;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
public class Runner {
int framerate = 30;
Animator a = new Animator();
JFrame j = new JFrame();
public Runner(){
j.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
j.add(a);
start();
j.setSize(a.getPreferredSize());
j.setVisible(true);
}
public void start() {
Timer t = new Timer(1000/framerate, new ActionListener() {
public void actionPerformed(ActionEvent e){
j.getComponent(0).paint(j.getComponent(0).getGraphics());
//The following line of code keeps the window locked to a preferred size
// j.setSize(j.getComponent(0).getPreferredSize());
}
});
t.start();
}
public static void main(String[] args){
Runner r = new Runner();
}
}
There are some serious mistakes in your code which could be the cause or a factor of your problem...
j.setSize(a.getPreferredSize()); is irrelevant, simply use JFrame#pack, you get better results as it takes into account the frame decorations
j.setSize(j.getComponent(0).getPreferredSize()); use JFrame#setResizable and pass it false instead...
NEVER do j.getComponent(0).paint(j.getComponent(0).getGraphics()); this! You are not responsible for the painting of components within Swing, that's the decision of the RepaintManager. Just call j.repaint()
super(SCREEN_X, SCREEN_Y);...just override the getPreferredSize method and return the size you want.
setLocation(80, 80); irrelevant, as the component is under the control of a layout manager
setVisible(true); (inside APanel)...Swing components are already visible by default, with the exception of windows and JInternalFrames
And finally...
public void paintComponent(Graphics g) {
requestFocusInWindow();
renderFrame(g);
}
There is never any need for this method to be made public, you NEVER want someone to be able to call it, this will break the paint chain and could have serious ramifications in the ability for the component to paint itself properly.
You MUST call super.paintComponent before performing any custom painting, otherwise you could end up with all sorts of wonderful paint artifacts and issues
Never modify the state of a component from within a paint method...while it "might" not be an immediate issue calling requestFocusInWindow(); within your paintComponent could have side effects you don't know about...
Instead, it should look more like...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
renderFrame(g);
}