Why does my Java repaint() method not work? - java

Heyo, for my college homework I need to draw some images and race them, but I am stuck even at drawing the images (or in this case, icons). I want to draw ANYTHING (that's why I have the drawLine method, just to test it out) as of right now on one of many JPanels, but my repaint() method does not call my paintComponent method, why?
import java.awt.*;
import javax.swing.*;
import java.lang.Math;
public class Races{
private int numberOfRacers;
public Races(int numberOfRacers){
this.numberOfRacers = numberOfRacers;
JFrame frame = new JFrame("Races - Name Surname");
Icon icon = new ImageIcon("races.jpg");
frame.setLayout(new GridLayout(numberOfRacers, 1));
frame.setSize(icon.getIconWidth()*20, (icon.getIconWidth()*2)*numberOfRacers);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try{
Thread.sleep(1000); // Sleeping 1 sec
System.out.println("Sleeping one second for the user!");
}catch(InterruptedException ie){
System.out.println(ie);
}
for(int i = 0; i < numberOfRacers; i++){
innerRacer racer = new innerRacer();
frame.add(racer.panel);
//JLabel iconLabel = new JLabel(icon);
//iconLabel.setHorizontalAlignment(JLabel.LEFT);
//panel.add(iconLabel);
Thread t = new Thread(racer);
t.start();
}
}
public static void main(String args[]){
if(args.length > 0 && Integer.parseInt(args[0]) > 0 && Integer.parseInt(args[0]) < 100){
Races races = new Races(Integer.parseInt(args[0])); // From command line number of racers
System.out.println("Number of racers: " + args[0]);
}else{
Races races = new Races(5); // Default number of racers
System.out.println("Number of racers: 5");
}
}
public class innerRacer extends JPanel implements Runnable{
JPanel panel;
Icon icon;
public innerRacer(){
panel = new JPanel();
panel.setBackground(Color.WHITE);
icon = new ImageIcon("races.jpg");
} //end of innerRacer constructor
#Override
public void run(){
repaint();
System.out.println("TEST");
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawLine(10, 20, 30, 40);
System.out.println("Why is this one not called?");
icon.paintIcon(panel, g, 0, 0);
}
} // end of innerRacer class
} //end of Races class
Thanks in advance.

You've added the JPanel that innerRacer contains rather than the JPanel that innerRacer is.
Change:
frame.add(racer.panel);
to:
frame.add(racer);
Also I see you are adding components to the frame after setting it to visible. When you do this in AWT/Swing they wont get automatically laid out or painted. You will need to follow with the line:
frame.revalidate();
Alternatively, the setVisible line could be moved down.
As general notes:
It's a good idea to stick to Java naming conventions - always initial caps for types.
JPanel panel; in innerRacer can be deleted.
Swing components should always be accessed from the AWT Event Dispatch Thread (EDT). Use java.awt.EventQueue.invokeLater.
Lots of tutorials suggest overriding JPanel without adding any components if you want to paint anything. This appears to be because it is opaque by default. Neither the API docs for JPanel nor even the OpenJDK source code mention opaqueness. Indeed, it isn't guaranteed. It's just a hack. Prefer setOpaque(true) to make a component opaque.
Having a class extend another and implement an interface (or implement multiple interfaces (not markers, Comparable, etc)) isn't great. Use a lambda method, method reference, inner class or just another outer class as appropriate.

Related

Why does super.repaint() not call paint method?

SOLUTION: I needet to add the JPanel into a JFrame. The calling of the paint method is bound to the visibility of the JFrame. thanks #matt
This is my first question so I am sorry if something is missing.
My Problem:
I want to create a class which extends JPanel so that I can draw things onto my JFrame. I also want it to be able to repaint itself. So far the run method works but super.repaint() does not. "test1" is being printed and "test2" not.
Thanks in advance :)
public class Main {
public static GUI gui;
public static void main(String[] args) {
gui = new GUI();
}
}
public class GUI extends JFrame{
public Draw draw;
public Thread drawT;
public GUI() {
draw = new Draw();
drawT = new Thread(draw);
drawT.start();
}
}
public class Draw extends JPanel implements Runnable{
#Override
public void run() {
while (true) {
System.out.println("test1");
super.repaint();
}
}
#Override
public void paint(Graphics graphics) {
System.out.println("test2");
Graphics2D g = (Graphics2D)graphics;
}
}
repaint schedules you component to be painted. If the component is not visible then paint will not get called.
A simplified way to look at it. Swing/AWT has the EventDispatchThread which continuously runs, processes events and paints components. When you call "repaint" it tells swing to schedule a paint event for your component. It doesn't call paint. When the EDT gets around to it, it will paint your component.
In your 'run' method you have a loop that prints out a statement to a terminal, "test1" and then it calls repaint(), and then it repeats. The slowest thing here is the System.out.println.
You have a really fast loop swamping AWT with repaints requests but that loop will run faster than the EDT loop will run paint events. So maybe you print test1 and call repaint 100 times before the EDT actually performs a single paint. I suspect if you look close, test2 is buried in your output somewhere.
import java.awt.*;
public class MainS{
int paints = 0;
int tags = 0;
public void runGui(){
JFrame frame = new JFrame("testing");
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
paints++;
}
};
frame.add(panel, BorderLayout.CENTER);
JLabel label = new JLabel("times painted ...");
frame.add(label, BorderLayout.NORTH);
frame.setSize(200, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer t = new Timer( 1000, evt->{
label.setText(paints + "/" + tags);
paints = 0;
tags = 0;
});
t.start();
new Thread( ()->{
while(true){
tags++;
panel.repaint();
}
}).start();
}
public static void main(String[] args){
EventQueue.invokeLater( new MainS()::runGui );
}
}
Here is a simple example, where the component actually gets repainted. It will display the number of times paintComponent was called and the number of times repaint was called at the top of the frame.

Paint Method is not Called

After making an instance in a previous frame, I'm trying to the background image on the next frame but as a result, I just saw the debugged result and found out that the paint method was not called. From what I know, the paint method is inherited by the JFrame class and with this logic, I've made it overrided. As I guess, the reason happen the logical error is from what I used the event handler and made the instance in the EventHandlerClass.
if(e.getActionCommand().equals(ButtonTo))
if(idString.equals("USER"))
{
{
if("1234".equals(pwSt))
{
System.out.println("Wellcome");
if(gs==null)
{
gs=new GameStart();
}
}
else
{
System.out.println("Confirm your password");
}
}
}
This is a code that If an action is performed it will make an instance(gs). After doing this, I noticed that the instance has been used as to make a new console frame.
class GameStart extends JFrame {
private Image screenImage;
private Graphics screenGraphic;
private Image introBackgroundImage;
private ImageIcon img;
GameStart()
{
JFrame jf=new JFrame("Game Set");
jf.setBounds(300, 300, 400, 200);
jf.setLayout(new BorderLayout());
JButton bt1=new JButton("Start");
JButton bt2=new JButton("Exit");
JPanel panel1=new JPanel();
panel1.add(bt1);panel1.add(bt2);
setContentPane(panel1);
jf.add(panel1, BorderLayout.SOUTH);
bt1.addActionListener(new Choice());
bt2.addActionListener(new Choice());
jf.setVisible(true);
img=new ImageIcon("./Images/backGroundImage.jpg");
System.out.println("1");
}
public void paint(Graphics g) {
screenImage=createImage(200, 200);
screenGraphic=screenImage.getGraphics();
screenDraw(screenGraphic);
g.drawImage(screenImage, 0, 0, null);
System.out.println("2");
}
public void screenDraw(Graphics g)
{
this.repaint();
System.out.println("3");
}
Now, With making a frame and some buttons, I expect to show all the numbers(1, 2, 3) that indicate the result but Just did number 1.
There are some errors in your code that I can see at first glance:
You're extending JFrame, but you're not adding any extra functionality to it, see: Extends JFrame vs. creating it inside the program. Instead, build your GUI towards the use of JPanels and override their paintComponent(...) method and not the paint(...) one.
You're breaking the paint-chain: After doing the above point, in paintComponent(), call super.paintComponent(...)
Maybe there are others but I'm currently busy and can't test your code, but the ones above should help with your issue.

Drawing Image on JPanel as Background

im trying to insert a gif as a background for my app. I cut all frames and renamed them f1/f2/f3/f4/f5/f6/..... I would use a timer to change the frame so it looks like an animation.
There is a total of 42 frames, so f42.png is the last frame. The code seems to be fine, but there is no result. Any help?
Global variables:
private String backgroundFile;
public JPanel backgroundPanel, areaImage;
private BufferedImage background;
private javax.swing.Timer timerBackground;
Constructor where the Timer is initialized:
public Game()
{
entryWindow();
this.setLayout(null);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Animation method code:
private void backgroundAnimation()
{
backgroundFile = "f"+backgroundNum+".png";
try{
background=ImageIO.read(new File(backgroundFile));
}
catch(IOException e)
{
}
backgroundPanel = new JPanel()
{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, 1100,800,null);
}
};
backgroundPanel.setBackground(Color.BLACK);
backgroundPanel.setBounds(0, 0, 1100, 800);
if (backgroundNum>42)backgroundNum++;
else backgroundNum=1;
add(backgroundPanel);
backgroundPanel.setVisible(true);
}
Action Listener for timer:
if (ae.getSource() == timerBackground)
{
backgroundAnimation();
}
In order to show JPanel, you need to add it to something like JFrame with an BorderLayout for instance, then you need to show the JFrame. JFrame is a application window, the JPanel can be only added and drawn on Window, it can't be viewed without something on which it can draw (like app Window). Beside that you don't need to create new JPanel each time the animation changes, just make a setter for the current image to show, and after assigning the image call repaint(), the ImagePanel could be like this:
public class ImagePanel extends JPanel {
private volatile BufferedImage image;
public void showImage(BufferedImage image) {
this.image=image;
repaint();
}
public void paintComponent(Graphics g) {
g.drawImage(image, 0,0,getWidth(),getHeight(),null);
}
}
add it to your JFrame at application start, also set the LayoutManager of JFrame to BorderLayout preferably, because without that your panel will have size(0,0) since you didn't set it, and it could be one of reasons why you don't see it (you can't see something which is 0 pixel in size, can you?).
Then in your timer just call the ImagePanel method public void showImage(BufferedImage image) with the image to show. If that's don't solve your problem, then post your entire code. As without that i'm just guessing, but those are common problems, so there's big chance you hit something from this.
I can see a few issues here
1. Assuming your Game class is extending JFrame, You need to add the JPanel to the ContentPane of the JFrame. Use one of the approaches setContentPane(backgroundPanel); or getContentPane().add(backgroundPanel)
You are not using a LayoutManager. So either use a LayoutManager or set the Size of the 'JFrame' and 'JPanel' explicitly using setBounds() method. I would recommend using a LayoutManager.
The JPanel or any Component for that matter does not automatically refresh itself. Once you change the image, you need to call repaint() on your JPanel.
You dont need to create a new JPanel every time you change the image. Just extend the JPanel and override the paintComponent()like you have done. Use the Timer to change the image of that single instance and call repaint() with every change.
The complete example, with hat output you are seeing will help understand the problem better and give you a solution. Please see How to create a Minimal, Complete, and Verifiable example
There are multiple problems here, but first let me answer your question:
You are creating a new JPanel and add it to the Game on every run through. That is wrong, since you add infinite panels to your Game
Also in your if/else you have a wrong condition. You increase the iterator when it is greater 42. You probably mean lesser than 42.
Here is how I would do it:
public class BackgroundPanel extends JPanel {
private int currImage = 0;
private BufferedImage[] backgroundImages;
public BackgroundPanel() {
int numberOfImages = 42;
backgroundImages = new BufferedImage[42];
for(int i = 1; i <= numberOfImages; i++) {
String backgroundFile = "f" + i + ".png";
backgroundImages[i] = ImageIO.read(new File(backgroundFile));
}
}
public void nextImage() {
/*if(currImage <= 42) currImage++;
else currImage = 1;*/
if(currImage++ > 42) currImage = 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImages[currImage], 0, 0, getWidth(), getHeight(), null);
}
}
You need to add this panel ONCE to your "Game":
//Somewhere in your Game
private BackgroundPanel backgroundPanel;
...
...
public Game() {
entryWindow();
this.setLayout(null);
backgroundPanel = new backgroundPanel();
backgroundPanel.setSize(getWidth(), getHeight());
add(backgroundPanel);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Your timer:
if (ae.getSource() == timerBackground) {
backgroundPanel.nextImage();
}
It's easier to put the background on JLabel. It requires only 3 lines of code and works fine! :) Hope it helps for anyone that will have the same problem :)
All you have to do is copy this code, change the name (i have all pictures in a folder called "Images") with any kind of Java supported picture/video/.... (just change the suffix .gif to your file format) and at last the size. Good luck! :)
public JLabel backgroundGIF;
backgroundGIF = new JLabel(new ImageIcon(getClass().getResource("Images/background.gif")));
backgroundGIF.setBounds(0,0,1100,800);
add(backgroundGIF);

How to have a class create its own instance of Graphics

I have a class called CharMove,in it are the paint(Graphics g) method, and some custom methods. The class should create a square,then move that square randomly around the screen. However,when I create two instances of this class in my World Class,only one square appears. First the square doesn't move but the new coord.'s are displayed,then after 5 runs the square begins to move randomly. I think the program is getting caught on the Graphics method because only one square is being created,when the CharMove class should be creating another instance of Graphics.I have searched online but can't find a way to create different instances of Graphics.Thanks in advance.
CharMove Class
import java.awt.*;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import java.util.Random;
public class CharMove extends JPanel {
int x = 250;
int y = 250;
public void paint(Graphics g) {
Graphics pane = (Graphics2D) g;
pane.setColor(Color.blue);
pane.fillRect(x, y, 10, 10);
}
public void movement(JFrame frame) {
for (int i=0;i<5;i++) {
try {
TimeUnit.SECONDS.sleep(1);
this.x = Getx(this.x,frame);
this.y = Gety(this.y,frame);
frame.repaint();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int Getx(int a, JFrame frame) {
Random rn = new Random();
int xnum = rn.nextInt(10)-5;
a += xnum;
System.out.println("x:" + a);
return a;
}
public int Gety(int b, JFrame frame){
Random rn = new Random();
int ynum = rn.nextInt(10)-5;
b += ynum;
System.out.println("y:" + b);
return b;
}
}
World Class
import java.awt.*;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import java.util.Random;
public class World {
public static void main(String[] args) {
JFrame game = new JFrame();
game.setTitle("Matrix");
game.setSize(500, 500);;
game.getContentPane().setBackground(Color.white);
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.setVisible(true);
CharMove char1 = new CharMove();
CharMove char2 = new CharMove();
game.add(char1);
game.add(char2);
char1.movement(game);
char2.movement(game);
}
}
In swing, all of your painting should be down in paintComponent(Graphics g) (rename your method)
To do animation, you should use a Swing Timer (w/ an ActionListener) to update the positions of your animated items. Once that's done, the timer should call repaint();
public void actionPerformed(ActionEvent ae) {
this.x = Getx(this.x,frame);
this.y = Gety(this.y,frame);
frame.repaint();
}
However,when I create two instances of this class in my World Class,only one square appears.
The default layout manager for a JFrame is a BorderLayout.
game.add(char1);
game.add(char2);
When you add components without specifying a constraint then both components are added to the CENTER. However, only one component can be added to the CENTER so only the last one added is displayed.
Try:
game.add(char1, BorderLayout.PAGE_START);
game.add(char2, BorderLayout.PAGE_END);
However when you do this the componens won't be displayed because they have a (0, 0) preferredSize. So you will also need to override the getPreferredSize() method of your CharMove class.
#Override
public Dimension getPreferredSize()
{
return new Dimension(300, 200);
}
Also, custom painting should be done in the paintComponent(...) method and you need to invoke super.paintComponent(...) at the start to clear the background.
The repaint() method in your movement() method should be on the panel, not the frame, since you are changing properties of the panel.
Each CharMove is essentially a JPanel which draws a single square of size 10 somewhere on itself when it is painted. You are adding two CharMove panels to the game JFrame (which in fact adds them to the default content pane, which has a subclassed BorderLayout). As you are not providing a layout constraints object, in fact both panels are being added to the BorderLayout.CENTER of the content pane, and the second is completely covering the first.
To correct this you should modify CharMove so that it paints all of the squares (eg by maintaining an array or some sort of collection of squares, and painting all of them in the paint method) and just add that one panel to the JFrame.
Apart from this issue, while you are animating the squares in the movement method you are blocking the Event Dispatch Thread, meaning that during the animation you won't be able to move any other windows or respond to any mouse clicks or other inputs. Take ControlAltDel's advice about using the Swing Timer for animation to correct this problem.

Java Changing Content Pane

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.

Categories

Resources