Line isn't being drawn to JPanel - java

This is an animation in which a given number of icons start on the left side of the frame and race across the screen to the right side. Each icon is drawn to its own JPanel that fills a row within a container's single-column GridLayout, and each JPanel races on its own thread. (Threads must be used, even though a Swing Timer might be a better approach.)
A finish line is also supposed to get painted onto the container, but it is not showing up. I have tried setting the Racer's JPanel opacity to false, but this doesn't work. How might I get the other threads to allow the finish line's painting to execute?
The code that isn't working:
gui = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(finishLineXPos, 0, finishLineXPos, windowHeight);
}
};
The full code:
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class Races2 {
private JFrame frame;
private JPanel gui; // to hold all components
private int finishLineXPos; // x-coordinate of finish line
private Icon racerImg;
private int racerImgWidth;
private int racerImgHeight;
private int numOfRacers;
private ArrayList<Racer> racers;
private Racer winner;
private int windowHeight;
private int windowWidth;
public Races(int num) {
numOfRacers = num;
racerImg = new ImageIcon("races.png");
racerImgWidth = racerImg.getIconWidth();
racerImgHeight = racerImg.getIconHeight();
windowHeight = racerImgHeight * numOfRacers;
windowWidth = racerImgWidth * 20;
finishLineXPos = racerImgWidth * 18; // two icon widths from the right
frame = new JFrame("Off to the Races - by Brienna Herold");
frame.setResizable(false); // prevents window resizing which affects painting
gui = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(finishLineXPos, 0, finishLineXPos, windowHeight);
}
};
gui.setLayout(new GridLayout(numOfRacers,1));
gui.setPreferredSize(new Dimension(windowWidth, windowHeight));
// Create and add racers to gui panel
racers = new ArrayList<Racer>();
for (int i = 0; i < numOfRacers; i++) {
Racer racer = new Racer();
gui.add(racer);
racers.add(racer);
}
// Start racers
for (Racer racer : racers) {
Thread racerThread = new Thread(racer);
racerThread.start();
}
frame.add(gui);
frame.pack();
frame.setVisible(true);
}
protected class Racer extends JPanel implements Runnable {
private int lastPosX;
private int posX;
public Racer() {
posX = 0;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
racerImg.paintIcon(this, g, posX, 0);
posX += Math.random() * 20;
}
#Override
public void run() {
// While the race has not been won yet, proceed with race
while (winner == null) {
repaint();
try {
Thread.sleep(100); // slows down racing a bit
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// If racer passes specified x-coordinate, set it as winner
if (posX >= finishLineXPos) {
System.out.println("Winner: " + this.getName());
winner = this;
}
}
}
}
}

Each icon is drawn to its own JPanel that fills a row within a container's single-column GridLayout
First you need to learn how to do custom painting. Start by reading the section from the Swing tutorial on Custom Painting for some basics. A couple of key points:
You need to override the getPreferredSize() method to the component has a preferred size so the layout manager can do its job. If you don't specify the preferred size then the size may be zero so there is nothing to paint.
A painting method is for painting only. You should NEVER modify the state of the component in a painting method since you can't control when Swing will repaint a component. So you need a method like setPositionX(...) to control the image location where it should be painted. Then in the Thread (or Timer) you invoke this method to change the location and invoke repaint() on the component.
A finish line is also supposed to get painted onto the container, but it is not showing up
Well you add all the Racer components to the top of the race panel so those components will cover the line painted on the panel.
You have been given one approach to use racer.setOpaque(false);
Another approach is to override the paint() method. (This is an exception to the general rule to do custom painting in the paintComponent() method). If you read the tutorial link I provided you will see that using this approach will cause the painting of the race panel to be done after all the Racer components have been painted.
frame.setResizable(false); // prevents window resizing which affects painting
It is not necessary for this. The only reason the painting is affected is because you are changing the state of the component in the painting method. See my comment above.
racerImg.paintIcon(this, g, posX, 0);
People generally use the drawImage(...) method to paint an image. There is no reason to create an Icon just to paint the image.
racerImg = new ImageIcon("races.png");
Don't use an ImageIcon to read a file. Using ImageIO.read(...) to read the image. Then just paint the image as described above.
and each JPanel races on its own thread.
That seems like a silly requirement because it brings a different kind of randomness to the race. You already have logic that generates a random distance for the image to move, so why do you need separate Threads for this. Instead you should just have a single Thread and then you iterate through all the Races and invoke the setPositionX() method suggested above. Then all Races will be repainted at the same time with there own random distance change.
Edit:
Having said all the above your code works fine for me with a single change:
posX = 0;
setOpaque(false);
You just need to make the Racer transparent in it's constructor.
As this appears to be a school assignment I guess you have to follow the rules, but you really should understand the problems with the assignment.
As everybody else in this posting has suggested, I still believe the better approach is to have an ArrayList or Racer objects (not components) that you can paint. I gave you a working example of painting Ball objects in your last question on this topic.

Related

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.

Strange repositioning using repaint();

I have a JSlider in a JPanel that return me a value of R-G-B .
I create it, in the Costructor of JPanel. I draw in same Panel (using paintComponent) a little circle, and I change his color using the Slider. I want that the color change in contemporany of slider shift.
So, i use the method repaint.. Next to Panel there is another Panel, with two button.. If I use method repaint in first panel , the buttons of second panel duplicated in the topLeft of First Panel. Why? Thank's you.
First Panel:
public class OptionsPanel extends JPanel {
static JSlider RBG = new JSlider(0,255);
OptionsPanel(){
this.setVisible(false);
this.setSize(350,1000);
this.setLayout(null);
this.setBackground(new Color(200,200,0));
Main.f1.add(this);
RBG.setVisible(true);
RBG.setSize(255,50);
RBG.setLocation(30,240);
this.add(RBG);
LotL lotl = new LotL();
Button save = new Button("Save");
save.setVisible(true);
save.setSize(100,40);
save.setLayout(null);
save.setLocation(60,300);
save.addActionListener(lotl);
save.setBackground(Color.yellow);
save.identificatore=3;
this.add(save);
}
boolean draw=false;
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
}
Second Panel:
public class FirstPanel extends JPanel{
FirstPanel(){
this.setVisible(true);
this.setSize(1000,1000);
this.setLayout(null);
this.setBackground(new Color(255,200,180));
Main.f1.add(this);
Button start = new Button("Start Game!");
Button options = new Button("Options");
LotL LotL = new LotL();
start.setVisible(true);
start.setSize(200,80);
start.setLayout(null);
start.setLocation(400,450);
start.addActionListener(LotL);
start.setBackground(Color.green);
start.identificatore=1;
this.add(start);
options.setVisible(true);
options.setSize(200,70);
options.setLayout(null);
options.setLocation(400,550);
options.addActionListener(LotL);
options.setBackground(Color.green);
options.identificatore=2;
this.add(options);
}
}
You've broken the paint chain...
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
Graphics is a shared resource, which gets passed to ALL the components that are painted during a given paint cycle.
One of the jobs of paintComponent is to prepare the Graphics context for painting, but filling with the components background color.
You MUST call super.paintComponent before performing any custom painting.
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
}
Also, there is never any need for paintComponent to be public, no one should ever be calling directly and NEVER modify the state of a component from within any paint method which may trigger a repaint, you will get yourself into a infinite loop which will eventually consume your CPU and make you computer unusable.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
You should also avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify

Custom Drawing loop in Swing

I'm trying to learn how to do custom GUI stuff in Java for a group project I'm working on. I've done user form type GUIs in the past so I know the gist of what I'm doing here, but the custom drawing stuff still confuses me.
I copied this code from online and I've been trying to figure out how it works, but I don't get why I can't loop the drawing method. As a simple test I'm trying to make the program draw an oval on my cursor. It draws the oval on the cursor, but only once on runtime and then does nothing.
How can I make this loop so I can continue to draw things? Or is there a different way I need to call/use the methods?
public class BombermanGUI extends JFrame {
public static final int CANVAS_WIDTH = 640;
public static final int CANVAS_HEIGHT = 480;
private DrawCanvas canvas;
public BombermanGUI() {
canvas = new DrawCanvas();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
Container cp = getContentPane();
cp.add(canvas);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setTitle("......");
this.setVisible(true);
}
private class DrawCanvas extends JPanel{
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
setBackground(Color.BLACK);
int x, y;
x = MouseInfo.getPointerInfo().getLocation().x - this.getLocationOnScreen().x;
y = MouseInfo.getPointerInfo().getLocation().y - this.getLocationOnScreen().y;
g.setColor(Color.YELLOW);
g.drawOval(x, y, 10, 10);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new BombermanGUI();
}
});
}
}
Painting a complex series of callbacks and responses to changes within the system. The first thing to remember is that you don't control the painting process, but rather make suggestions to the system so that it can make decisions about what and when it should repaint...
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details.
Painting is a destructive process. It is assumed that when a repaint occurs, that you will repaint the entire state of the current component. This means that you will need some kind of model which maintains all the content that needs to be painted...
Have a look at 2D Graphics, in particular, have a look at the section on Shape
MouseInfo is a seriously crappy way to detect the location of the mouse for this purpose, instead, you should be using a MouseListener and/or MouseMotionListener to detect mouse events.
Basically, when the user presses a mouse button, you would record the location of the mouse press. When the mouse is moved, you would calculate the width and height of the movement relative to the mouse press and update the "current" shape. You would call repaint to request that the UI be updated and paint this shape via the paintComponent method (painting all the previous shapes first).
When the mouse button is released, you would commit the "current" shape to the model, so it will be painted every time paintComponent is called.
THIS IS tobais_k ANSWER IM ANSWERING TO CLOSE THE QUESTION!
Either add an event listener and have it call the repaint method, e.g. a mouse motion listener for tracking your mouse cursos, or have some thread run your game and trigger repaint in regular intervals.

How to set the background in a JPanel without using the super call in paintComponent()

In this program, I want to draw a series of lines that interact to form a web. Each time the timer ticks, a line is drawn. Therefore, I cannot have the super.paintComponent(g) call in the paintComponent() because I need the previous lines to appear. However, I would like to set the background colour and as far as I've found, the setBackground() method can be called only if the super call is first made. I am not sure if the fillRect method would work either because it would draw a rectangle over the old line each time. I tried having the setBackground() method in the constructor, and it did not work either.
Here is my code:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class prettyWebPanel extends JPanel implements ActionListener {
Timer time = new Timer(100,this);
private Color colour1 = Color.black;
private Color colour2 = Color.white;
JButton start = new JButton("Start");
int j = 0;
public prettyWebPanel() {
setPreferredSize(new Dimension (550,550));
this.add(start);
start.addActionListener(this);
setBackground(colour1);
}
public void paintComponent(Graphics g) {
setBackground(colour1);
setForeground(colour2);
if (j<490) g.drawLine(20, 20+j, 20+j, 500);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == start) time.start();
else if (e.getSource() == time) {
j+=10;
repaint();
}
}
}
because I need the previous lines to appear.
Then you need to do incremental painting. See Custom Painting Approaches for two common ways to do this:
Keep a List of objects to paint and repaint them every time
Do the painting onto a BufferedImage.

Categories

Resources