I'm making a small game using Java Swing. A class Game extends JPanel and implements ActionListener, a Timer in it calls repaint() to update the screen, then create a JFrame and add Game to it.
Then I noticed when I make the window full screen, the screen is updated once when paint() is called twice. This is weird! Any help would be appreciated.
Here is an SSCCE, the example creates a variable count, and paint count then count+=1; in repaint(). When not full screen, it shows 0 1 2 3 4... When full screen, it shows 4 6 8 10...
Example code here:
package test.swing.FullScreenDropFrameRate;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
class Game extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
private int count = 0;
public Game() {
new Timer(1000, this).start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paint(Graphics g) {
// bg
g.setColor(Color.black);
g.fillRect(0, 0, getSize().width, getSize().height);
// info string
g.setColor(Color.white);
g.setFont(new Font("serif", Font.BOLD, 30));
g.drawString(""+count, 0, 30);
count += 1;
g.dispose();
}
}
public class TestFullScreenFrameRate {
public static void main(String[] args) {
JFrame jf = new JFrame("Snake");
jf.setBounds(100, 35, 800, 600); // x, y, width, height
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(new Game());
jf.setVisible(true);
}
}
OK, so the problem is that I shouldn't put anything which updates the game state in paint(). Java calls it when needed. Thanks to #Andrew Thompson.
Some other mistakes, improvements and documentation:
1)Documentation - JComponent - paint(): Invoked by Swing to draw components. ... A subclass that just wants to specialize the UI (look and feel) delegate's paint method should just override paintComponent.
2)Documentation - Font - SERIF: A String constant for the canonical family name of the logical font "Serif". It is useful in Font construction to provide compile-time verification of the name.
3)Oracle - about Painting
4)Documentation - Graphics - dispose(): For efficiency, programmers should call dispose when finished using a Graphics object only if it was created directly from a component or another Graphics object.
5)A Java Game Tutorial on Youtube: Bad example! DO NOT use paint() and g.dispose() here!!!
Related
For some reason Graphics2D won't generate the background I am trying to set. I think the problem is something to do with renderer sending the information back to Simon the main class. I can't seem to find a solution and have been looking for hours online. If anyone know graphics 2D well or sees the issue with the code and can help that would be greatly appreciated.
package Simon;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
public class TestingSimon implements ActionListener {
//Creating object simon
public static TestingSimon simon;
//Making integers for Width and Height for JFrame
public static final int WIDTH = 800, HEIGHT = 800;
//making a renderer for graphics2D
public Renderer renderer;
public TestingSimon() {
//Creating JFrame
JFrame frame = new JFrame("Simon Says");
//using animation for a timer
Timer timer = new Timer(20,this);
//assigning renderer to the method I create in java class Renderer
renderer = new Renderer();
//setting a size for the JFrame
frame.setSize(WIDTH + 15, HEIGHT + 35);
frame.setVisible(true);
frame.add(renderer);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
timer.start();
}
public static void main(String[] args) {
simon = new TestingSimon();
}
#Override
public void actionPerformed(ActionEvent e) {
//setting it to repaint when action event occurs
renderer.repaint();
}
public void paint(Graphics2D g) {
//trying to set background to grey using Graphics2D
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.GRAY);
}
}
//Java package Renderer
package Simon;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
/**
*
* #author chasepflueger
*/
#SuppressWarnings("serial")
//extending JPanel
public class Renderer extends JPanel
{
/**
*
* #param g
*/
//trying to implement paintComponent to Simon
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
if(TestingSimon.simon != null)
{
TestingSimon.simon.paint((Graphics2D) g);
}
}
}
//trying to set background to grey using Graphics2D
Painting the background of a component is already support by all Swing components. All you do is set the background Color:
renderer = new Renderer();
renderer.setBackground(Color.GREY); // also try Color.RED to see the difference
Painting is a function of Java components:
public void paint(Graphics2D g) {
//trying to set background to grey using Graphics2D
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.GRAY);
}
You can't just add a method to any class and expect painting to work. Your TestingSimon class is not a Component so that does nothing. Get rid of the code.
Read the section from the Swing tutorial on Custom Painting for more information and working example you can download and play with to learn the basics.
package Main;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Main extends JFrame{
public static void main(String[] args) {
int width = 800;
int height = 600;
String title = "Test";
JFrame display = new JFrame();
display.setTitle(title);
display.setSize(width, height);
display.setVisible(true);
display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
g.setColor(Color.white);
g.drawLine(0, 100, 800, 300);
getContentPane().setBackground(Color.black);
}
}
I'm using Java's JFrame. So this isn't recognising the paint method and cant figure out why. I've been looking on YouTube videos and having a look to see if anyone has had similar problems, however everything I've found doesn't seem to help the problem.
when i set the background colour in the main part, it works, bit in paint, it doesn't seem to do anything and leaves it blank.
Its a white line over a black background, so i should easily be able to see it.
Admittedly, I don't know much about Swing (I prefer JavaFX). However, it's clear that your Main class is a JFrame, so you should not make a new one within it. All of those methods you call on display are built in your current class. Basically, within your JFrame you made a new JFrame. However, your paint method was being called on the parent JFrame, which you never made visible. This solves your problem (you may have to fullscreen the window):
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Main extends JFrame{
public static void main(String[] args) {
new Main();
}
public Main() {
int width = 800;
int height = 600;
String title = "Test";
setTitle(title);
setSize(width, height);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.WHITE);
g.drawLine(100, 100, 800, 300);
getContentPane().setBackground(Color.black);
}
}
You are creating an instance of JFrame with
JFrame display = new JFrame();
But the JFrame class has no logic to draw a white line on a black background. That logic is in your custom class Main. So instead, you should create an instance of Main:
JFrame display = new Main();
However, that change along still won't fix the problem because you are setting the background color of the "content pane" but trying to draw directly on the JFrame itself. The preferred solution here is to extend JPanel instead of JFrame and override its paintComponent() method. Then create an instance of your new class to use as the content pain:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainPanel extends JPanel{
public static void main(String[] args) {
int width = 800;
int height = 600;
String title = "Test";
JFrame display = new JFrame();
display.setTitle(title);
display.setSize(width, height);
display.setVisible(true);
display.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
display.setContentPane(new MainPanel());
}
public MainPanel() {
setBackground(Color.black);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.white);
g.drawLine(0, 100, 800, 300);
}
}
Notes:
I call setBackground() in the constructor because it does not rely on the Graphics instance passed to paintComponent(). This also avoids the overhead of another function call for each render.
In paintComponent(), I call super.panitComponent(). This allows JPanel to clear the area to be painted and any other necessary initialization.
As the title says I'm trying to make add a keylistener to a JPanel. So far the only way I got it working was by adding an empty textfield and clicking on it. Now I don't want an empty textfield in my JPanel so I want to add the keylistener to the panel itself.
Here is the class I'm talking about:
package cookieClicker;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class CookieView extends JPanel
{
private CookieModel cm;
private ImageIcon cookie;
public Rectangle rect;
public CookieView(CookieModel cm)
{
this.cm = cm;
this.setFocusable(true);
this.requestFocusInWindow();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
cookie = new ImageIcon("Untitled-1.png");
g.setColor(Color.ORANGE);
g.drawImage(cookie.getImage(), this.getWidth() / 2 - 100, this.getHeight() / 2 - 100, 200, 200, this);
rect = new Rectangle(this.getWidth() / 2 - 100, this.getHeight() / 2 - 100, 200, 200);
}
public void addListener(MouseListener m, KeyListener k)
{
this.addMouseListener(m);
this.addKeyListener(k);
}
}
Does anyone know how to make this work?
panel is focused
How do you know the panel is focused?
The requestFocusInWindow() method only works when the frame is already visible at the time the method is invoked. So invoking the method in your constructor won't do anything.
The basic code should be:
CookieView panel = new CookieView();
JFrame frame = new JFrame();
frame.add(panel);
frame.pack();
frame.setVisible(true);
panel.requestFocusInWindow();
Also you should make sure all the code is execute on the Event Dispatch Thread.
However, you should probably not even be using a KeyListener. In most cases Swing was designed to be used with Key Bindings. Read the tutorial to see if key bindings will work for you.
Finally, you should NOT be reading an Image file in the paintComponent() method. The painting methods are called whenever Swing determines a component needs to be repainted so it is inefficient to keep reading the image over and over.
Whatever I do, I can not display rectangle/line/oval on the screen. I checked other sources where they paint graphics, but when I even execute those codes, I don't get any graphics displayed on the windows. Below is the example from the text book.
import java.awt.*;
import javax.swing.*;
class PlotGraph
{
public static void main (String [] args) {
JFrame win;
Container contentPane;
Graphics g;
win = new JFrame("testing");
win.setSize(300,200);
win.setLocation(100,100);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
contentPane = win.getContentPane();
g = contentPane.getGraphics();
g.drawRect(10, 30, 50, 50);
}
}
Ouch. You should change your text book then. First of all, all the accesses to Swing components must be done in the event dispatch thread.
Second, you should not get the graphics of a component and paint on it. Instead, you should extend a JComponent or JPanel, override its paintComponent(Graphics) method, and paint using the Graphics object passed as argument (and which is in fact a Graphics2D instance).
That's not how graphics work in Swing.
You need to add a component to your frame, not just draw on it. You never want to draw directly on the frame. The reason why it's not doing anything is because your drawing code is being overridden.
If you want your component to have custom drawing code, make a subclass of JComponent and override the paintComponent(Graphics) method. An example of how you should do this is as follows:
import java.awt.*;
import javax.swing.*;
class PlotGraph {
public static void main(String[] args) {
JFrame win;
win = new JFrame("testing");
win.setSize(300, 200);
win.setLocation(100, 100);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
win.setContentPane(new MyComponent());
}
}
class MyComponent extends JComponent {
#Override
public void paintComponent(Graphics g) {
g.drawRect(10, 30, 50, 50);
}
}
I would highly encourage you to check out the Java GUI tutorial online.
I have been coding up Java with Netbeans for about a year now, and have written a lot of data manipulation code which plots graphs on-screen. I generally plant a JPanel object in my main window, write custom painting code, and call the repaint() method as needed.
But today, for the first time, I tried to invoke a repaint on a panel from a class (object) other than the one that contained the panel. Although the compiler found nothing wrong with this, and in debugging mode, it single-stepped properly to the exterior call to the repaint, no repaint actually occurred, and the code did not actually step into the repaint method.
I wrote a minimalist program to demonstrate the problem, as shown below (Main is ommitted, since it only contains code to set up the two on-screen panels.)
--- Description of classes, first contains the drawing surface, other the repaint call ---
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Panel1 extends JComponent
{
GraphPnl graphPnl;
boolean colorFlag;
public Panel1()
{
setLayout(null);
colorFlag = true;
graphPnl = new GraphPnl();
graphPnl.setBounds(10, 10, 110, 110);
graphPnl.setBackground(Color.black);
add(graphPnl);
}//Panel1()
public class GraphPnl extends JPanel
{
//just draws a line segment, toggling color
#Override
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (colorFlag) {g2.setColor(Color.red);} else {g2.setColor(Color.green);}
g2.drawLine(10, 10, 50, 50);
}//paint
}//GraphPnl
}//Panel1
import javax.swing.*;
import java.awt.event.*;
public class Panel2 extends JComponent
{
JButton testBtn;
TestAction testAction;
Panel1 p1;
public Panel2()
{
p1 = new Panel1();
testBtn = new JButton("Click");
testBtn.setBounds(10, 10, 80, 30);
add(testBtn);
testAction = new TestAction();
testBtn.addActionListener(testAction);
}//Panel2()
public class TestAction implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
p1.colorFlag = ! p1.colorFlag;
p1.graphPnl.repaint();
}
}//TestAction
}//Panel2
If anyone has any insights into this, or knows of a workaround, I'd be very happy to hear
from you.
Thanks in advance for any insights.
John Doner
Main is ommitted, since it only contains code to set up the two on-screen panels.)
Well, by definition when you have a problem you don't know what code is or isn't relative until the problem is solved. So a complete SSCCE should be posted.
As a wild guess I would say your component has a size of 0 so there is nothing to paint.
I generally plant a JPanel object in my main window, write custom painting code, and call the repaint() method as needed
You probably got lucky because you added the panel to the center of a BorderLayout which automatically gives the panel all the space available to the frame.
trashgod's example shows one way to set the preferred size of a custom component. Another way is to override the getPreferredSize() method to return the proper value.
You really should learn how to use layout manager rather than using null layouts and you will avoid problems like this in the future. There is no need to use a null layout unless you have a drag/drop type of application.
"Swing programs should override paintComponent() instead of overriding paint()."—Painting in AWT and Swing: The Paint Methods.
main is ommitted, since it only contains code to set up the two on-screen panels.
Verify that you construct your GUI on the EDT, as shown in the article Initial Threads.
Addendum: Here's an example showing both principles:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/questions/4282159 */
public class GraphPanel extends JPanel {
private boolean colorFlag;
public GraphPanel() {
this.setPreferredSize(new Dimension(640, 480));
}
public void toggle() {
colorFlag = !colorFlag;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (colorFlag) {
g2.setColor(Color.red);
} else {
g2.setColor(Color.blue);
}
g2.drawLine(0, 0, getWidth(), getHeight());
}
private void display() {
JFrame f = new JFrame("GraphPanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this, BorderLayout.CENTER);
f.add(new ControlPanel(this), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new GraphPanel().display();
}
});
}
}
class ControlPanel extends JPanel {
public ControlPanel(final GraphPanel gp) {
this.add(new JButton(new AbstractAction("Click") {
#Override
public void actionPerformed(ActionEvent e) {
gp.toggle();
gp.repaint();
}
}));
}
}
Addendum: As noted in #camickr's comment, A Visual Guide to Layout Managers may help guide your layout selection.
I believe that when you are painting a JComponent, the clip region is set to that JComponent. So if other components try to paint (or if you call their repaint), they won't, because of the clipping.