While I was making a 2D Game in Java, a (atleast to me) very strange problem occured. I've provided a runnable, shortened example which reproduces my problem. When the x-coordinate of the red square is between 100 and 120 it should draw the string "Sample Text" above the square. However, you will see if you run the code, the window freezes completely for a few seconds. After the lag you can go over the area without problems, and the text will be shown. This problem only occurs when the program draws a String above the square. If I change the code so only another square appears above the red one, there's no lag. (I commented that in my code)
Any help would be appreciated.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
public class MyExample extends JApplet {
int x = 10;
int y = 150;
public void init() {
setFocusable(true);
requestFocus();
Action right = new moveRight();
Action left = new moveLeft();
JRootPane rootPane = getRootPane();
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
rootPane.getActionMap().put("right", right);
rootPane.getActionMap().put("left", left);
getContentPane().add(new Paint());
}
protected class moveRight extends AbstractAction {
public void actionPerformed(ActionEvent e) {
x+=3;
repaint();
}
}
protected class moveLeft extends AbstractAction {
public void actionPerformed(ActionEvent e) {
x-=3;
repaint();
}
}
class Paint extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x,y,10,10);
g.setColor(Color.BLACK);
g.drawLine(100,100,100,200);
g.drawLine(129,100,129,200);
if(x>100&&x<120) {
g.setFont(new Font("TimesRoman", Font.PLAIN, 15));
g.setColor(Color.BLACK);
g.drawString("Sample Text",x-30,y-25);
//g.fillRect(x,y-15,10,10); - This work fine if you remove the g.setFont and the drawString
}
}
}
}
This has to do with the fact that you are trying to load the font within the paintComponent method AND the underlying API trying to load the font and it's details before it can paint them.
I did think you could just pre-cache the font using something like...
class Paint extends JPanel {
private Font paintFont;
public Paint() {
paintFont = new Font("TimesRoman", Font.PLAIN, 15);
setFont(paintFont);
}
But in my testing, this still didn't work, what I actually ended up doing was adding a call to getFontMetrics, which seems to force the API to load the font and it's properties into memory, making it render immediately, for example
class Paint extends JPanel {
private Font paintFont;
public Paint() {
paintFont = new Font("TimesRoman", Font.PLAIN, 15);
setFont(paintFont);
getFontMetrics(paintFont);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(x, y, 10, 10);
g.setColor(Color.BLACK);
g.drawLine(100, 100, 100, 200);
g.drawLine(129, 100, 129, 200);
if (x > 100 && x < 120) {
System.out.println("...");
//g.setFont(paintFont);
g.setColor(Color.BLACK);
g.drawString("Sample Text", x - 30, y - 25);
//g.fillRect(x,y-15,10,10); - This work fine if you remove the g.setFont and the drawString
}
}
}
Now, this will make your application load slightly slower, but will allow it to run faster as you've moved the loading of the font out of the paint cycle
On my system TimesRoman doesn't exist.
I used Times New Roman with no problems. I also tried a few other fonts and they worked no problem. So I guess specifying an invalid font name causes a hiccup?
I also created the font once and cached it:
Font font = new Font("Times New Roman", Font.PLAIN, 15);
and then in the painting method used:
g.setFont( font );
Also, don't forget the super.paintComponent(g);
This question contains the code I used to list the fonts on my machine: Java JTextArea font
Related
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!!!
I have been doing a ton of research and none of the questions I found really answered my question and that is why I am making this post.
I want to create a program that will have a circle, a "planet" orbit around a another circle, a "Sun."
I have the static gui set up, but nothing I have found in my book or online really helps solves the orbit problem. Any ideas?
NOTE: eventually the program needs to be multithreaded (one for the planet and one for the Sun) but I want to break the problem down before I get back into trying to get that to work so for now please disregard it.
GUI:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class PlanetsGUI extends JPanel
{
private static final long serialVersionUID = 1L;
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
setBackground(Color.BLACK);
paintSun(g);
paintPlanet(g);
}
public void paintSun(Graphics g)
{
super.paintComponent(g);
//create circle and fill it as yellow to represent the sun
g.setColor(Color.YELLOW);
g.drawOval(100, 75, 75, 75);
g.fillOval(100, 75, 75, 75);
} //end paintSun
public void paintPlanet(Graphics g)
{
//create circle and fill it as blue to represent the orbiting planet
g.setColor(Color.BLUE);
g.drawOval(35, 50, 50, 50);
g.fillOval(35, 50, 50, 50);
}//end paintPlanet
}//end class PlanetsGUI
MAIN:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JFrame;
public class OrbitingPlants_main
{
private static final ExecutorService execute + Executors.newFixedThreadPool(2);
public static void main(String[] args)
{
PlanetsGUI planet = new PlanetsGUI();
JFrame frame = new JFrame();
frame.setTitle("Orbiting Planets");
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(planet); //add panel onto frame
frame.setVisible(true);
//PlanetsLogic r = new PlanetsLogic();
//Thread sun = new Thread(sun);
//sun.start();
//execute.submit(new );
}//end main
}//end class
You're hard-coding where the images are being drawn, for example:
g.setColor(Color.BLUE);
g.drawOval(35, 50, 50, 50);
g.fillOval(35, 50, 50, 50);
making for images that can never change.
Instead get rid of the hard coded numbers and use variables, values that can change:
g.setColor(Color.BLUE);
// variables used below are fields declared in the class
g.drawOval(planetX, planetY, planetW, planetW); // probably don't need this line
g.fillOval(planetX, planetY, planetW, planetW);
and then change the values in your thread (or better, Swing Timer).
As far as "orbiting", that would be using the equations of a circle (or ellipse if you wanted to be extremely precise), and perhaps using a parametric equation to determine your planetX and planetY values.
Below is a code from "Simon" in which I have running showing the correct segments that are supposed to be shown but I am have trouble with my keyPress and it will not light up using the arrow keys when I use it. Not really a great coder and I really need some help with this.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Arc2D;
import javax.swing.Timer;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Simon extends JFrame implements KeyListener {
// public DrawStuff game;
public static Simon simon;
Graphics2D g2;
Graphics2D g3;
Graphics2D g4;
Graphics2D g5;
public JFrame frame = new JFrame();
public JPanel panel = new JPanel();
private ActionListener timerAction;
public Simon() {
frame = new JFrame();
panel = new JPanel();
// Sets up JFrame
frame.setTitle("Simon Says");
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawStuff game = new DrawStuff();
panel.setLayout(new BorderLayout());
panel.add(game, BorderLayout.CENTER);
frame.add(panel);
frame.addKeyListener(this);
// game.setFocusable(true);
// game.requestFocus();
frame.setVisible(true);
}
public class DrawStuff extends JPanel {
public void paint(Graphics g) {
g2 = (Graphics2D) g;
g3 = (Graphics2D) g;
g4 = (Graphics2D) g;
g5 = (Graphics2D) g;
// assume d == 145 && e == 90
g2.setPaint(Color.BLUE.darker());
g2.fill(new Arc2D.Double(45, 45, 400, 400, 145, 90, Arc2D.PIE));
g3.setPaint(Color.RED.darker());
g3.fill(new Arc2D.Double(45, 45, 400, 400, 235, 90, Arc2D.PIE));
g4.setPaint(Color.GREEN.darker());
g4.fill(new Arc2D.Double(45, 45, 400, 400, 325, 90, Arc2D.PIE));
g5.setPaint(Color.YELLOW.darker());
g5.fill(new Arc2D.Double(45, 45, 400, 400, 55, 90, Arc2D.PIE));
}
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
g5.setPaint(Color.YELLOW);
//Color YELLOW = Color.WHITE;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
g2.setPaint(Color.BLUE.darker());
//Color BLUE = Color.WHITE;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
g3.setPaint(Color.RED.darker());
//Color RED = Color.WHITE;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
g4.setPaint(Color.GREEN.darker());
//Color GREEN = Color.WHITE;
}
panel.repaint();
}
public void keyReleased(KeyEvent e) {
/**
* if (e.getKeyCode() == KeyEvent.VK_UP) { Color YELLOW = Color.YELLOW;
* repaint(); }
*
* if (e.getKeyCode() == KeyEvent.VK_LEFT) { Color BLUE = Color.BLUE;
* repaint(); }
*
* if (e.getKeyCode() == KeyEvent.VK_DOWN) { Color RED = Color.RED;
* repaint(); }
*
* if (e.getKeyCode() == KeyEvent.VK_RIGHT) { Color GREEN = Color.GREEN;
* repaint(); }
**/
}
#Override
public void keyTyped(KeyEvent e) {
}
// TODO Auto-generated method sub
public static void main(String[] args) {
simon = new Simon();
}
}
You're doing a bit of guessing here, and that's not going to work. Instead, I recommend that you:
First and foremost, use Key Bindings, not a KeyListener as Key Bindings allow you to get around the KeyListener focus issue without using a kludge. You can find the Key Binding tutorial here: Key Binding Tutorial.
Next, never try to extract a Graphics object from a painting method and use it outside of the painting method, unless within a method that is directly called by the painting method. That Graphics object that you've extracted and are trying to use within your MouseListener are not long-lived objects, and might even be null when you try to use them.
Instead, change the state of your class's variables within your MouseListener and then use the state of those variables within your painting method to tell it what to paint. Look at the Performing Custom Painting Tutorial for more on how to draw with Swing.
Make sure that when a proper key is pressed that you draw that one pie slice darker, and all the other pie slices normal.
Other issues:
your class extends JFrame and yet you create another JFrame. Don't do this, either have and use one JFrame or the other, but don't have both as that will only confuse you later.
Your drawing JPanel should have its paintComponent method overridden not its paint method.
You need to call the super's painting method within your override. So if you're overriding paintComponent like I have suggested, call super.paintComponent(g); on the first line of your override, as this will allow the JPanel to do its house-keeping painting, including erasing old dirty images.
For an example of just what I'm talking about, please see this answer of mine to a similar question. This will create this GUI:
I am currently working on a project, I get this error Java.lang.NullPointerException, I undrestand that this error happen when you try to refer to a null object instance, but what I do not know, is how I can fix it.
This is my code:
public void paint(Graphics g) {
g.setColor(Color.red);
g.drawOval(150, 150, 10, 10);
}
/** Main Method **/
public static void main(String [] args) {
Run run = new Run();
run.paint(null);
}
Please help me with a solution and also explain it, so that I learn it. Many thanks in advance. Andre
You may not pass null to your paint method! Here is a small example how to do it:
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
class MyCanvas extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval (10, 10, 200, 200);
}
}
public class DrawOval {
public static void main(String[] a) {
JFrame window = new JFrame();
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas());
window.setVisible(true);
}
}
You almost never call paint methods (including paintComponent) directly as this is what the JVM should be doing.
Don't override the paint(Graphics g) method of a JComponent or JComponent derived class such as a JPanel if you can avoid it. This method, paint, is responsible for not only painting the component but also its borders and its child components, and if not done carefully, overriding this method will not infrequently result in unwanted side effects.
Later when you want to do graphics animation, overriding paint(Graphics g) will result in jerky graphics since it does not do double buffering by default.
By overriding the paintComponent(Graphics g) method instead you fix these issues.
Don't forget to call the super's paintComponent(g) method in your override to erase any unwanted previously drawn images.
Read the Swing Graphics tutorials, both the basic and advanced tutorials. Links at the bottom.
Better code:
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterCanvas extends JComponent {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(10, 10, 200, 200);
}
public static void main(String[] a) {
MyBetterCanvas canvas = new MyBetterCanvas();
canvas.setPreferredSize(new Dimension(300, 300));
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(canvas);
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Better Still:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterStillCanvas extends JComponent {
private static final int PREF_W = 500;
private static final int PREF_H = 500;
private static final int OVAL_X = 10;
private static final int OVAL_Y = OVAL_X;
private static final Paint BG_PAINT = new GradientPaint(0, 20,
Color.black, 20, 0, Color.darkGray, true);
private static final Paint FILL_PAINT = new GradientPaint(0, 0,
Color.blue, 20, 20, Color.red, true);
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth out graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// let's draw something funky
g2.setPaint(BG_PAINT);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(FILL_PAINT);
// avoid use of "magic" numbers
g.fillOval(OVAL_X, OVAL_Y, getWidth() - 2 * OVAL_X, getHeight() - 2
* OVAL_Y);
}
// a cleaner way to set the preferred size of a component
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public static void main(String[] a) {
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(new MyBetterStillCanvas());
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Which displays as:
Tutorials:
Java Tutorials, Really Big Index
Java Swing Tutorials
Basic Swing Graphics Tutorial: Lesson: Performing Custom Painting
More Advanced Graphics Article: Painting in AWT and Swing
You are not doing it the right way. In order to use graphics in java you need to build upon Swing/AWT components. Currently you are passing Graphics as null.
run.paint(null);
You need to implement this using JFrame and other swing components.
Since you are sending null to paint, Graphics g contains null (points to nowhere).
Then inside paint(...) you call setColor(...) on g, which is null. null.setColor(...) causes NullPointerException.
I want to change a JPanel-object in an applet at startup/init. I can't figure out how to do this. I've made a simple example of my problem in which I clear the JPanel. It does not work when it is called by the init() method but it works when I press the button. What can I do to change the JPanel at startup/init?
import javax.swing.JApplet;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class TestStartUpApplet extends JApplet {
JPanel panel;
#Override
public void init() {
System.out.println("Init");
erasePanel();
}
private void erasePanel() {
Graphics g = panel.getGraphics();
g.clearRect(0, 0, 117, 48);
}
public TestStartUpApplet() {
getContentPane().setLayout(null);
panel = new JPanel();
panel.setBackground(Color.RED);
panel.setBounds(35, 36, 117, 48);
getContentPane().add(panel);
JButton btnTest = new JButton("Test");
btnTest.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
erasePanel();
}
});
btnTest.setBounds(35, 108, 117, 25);
getContentPane().add(btnTest);
}
}
Works just for me:
public class AppletPaintTest extends JApplet {
#Override
public void init() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
setLayout(new BorderLayout());
ImagePane pane = new ImagePane();
pane.setBackground(Color.RED);
pane.setOpaque(false); // Little trick
add(pane);
}
});
}
#Override
public void start() {
super.start();
}
public class ImagePane extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
Insets insets = getInsets();
int x = insets.left;
int y = insets.top;
int width = getWidth() - 1 - (insets.left + insets.right);
int height = getHeight() - 2 - (insets.top + insets.bottom);
g2d.setColor(getBackground());
g2d.fillRect(x + 10, y + 10, width - 20, height - 20);
g2d.dispose();
}
}
}
question
no idea why do you want to clear empty JPanel without any custom painting
what's wrong with JPanel with red Background
you clear JApplet before became visible on the screen
doesn't works correctly, because doesn't works anything
suggestions
don't use AbsoluteLayout, use LayoutManager instead
Graphics g = panel.getGraphics(); is usefull for printing to the printer, or save Graphics to the image, don't use this method for another reason
read how JApplet works
maybe to use JFrame in the case that you don't want to publish your GUI to the web browser
maybe to read tutorial about 2D Graphics
Whenever you do any custom painting outside of the usually system calls to your paint() or paintComponent() methods, you must call invalidate() on any components you wish to repaint. So for your erasePanel() method, I would suggest setting some flag then calling panel.invalidate(). Then inside your panel's paintComponent() method, you can check the flag to see if you need to draw the introductory picture or just leave the panel blank.
Ok, it seems that the problem was that my coding was just bad. I wanted to change a panel object by a method from another class and that's not the way to do it. I rewrote my code and made a panel class in which the painting is done using paintcomponent. I now use objects of this panel class and it shows the graphics I want at startup.
Thanks for the help!