How repaint method differs for a Jpanel and Jframe? - java

i have the following code for animating a ball from top left corner towards the bottom right corner.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class MainFrame{
int i=0,j=0;
JFrame frame = new JFrame();
public void go(){
Animation anim = new Animation();
anim.setBackground(Color.red);//Why color is not changing to red for the panel.
frame.getContentPane().add(anim);
frame.setVisible(true);
frame.setSize(475,475);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
for(i=0,j=0;i<frame.getHeight()&&j<frame.getWidth();++i,++j){
anim.repaint();//Main problem is here,described below.
try{
Thread.sleep(50);
}
catch(Exception ex){}
}
}
public static void main(String[] args) {
MainFrame mf = new MainFrame();
mf.go();
}
class Animation extends JPanel{
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D)g;
g.fillOval(i,j,25,25);
}
}
}
Questions
When i do anim.repaint() inside the method go i don't get the ball animating from top left corner to bottom right corner but it gets smeared down the path.But if i replace it with frame.repaint() i get the desired result that is a moving ball.So what is the difference between these two calls to repaint?
Why the color of panel is not changing after anim.setBackground(Color.red); in go method?
If you run this programe you will find that the ball is not exactly going at the bottom edge,so how can i acheive that?

);//Why color is not changing to red for the panel
You should always invoke super.paintComponent(g) when you override the paintComponent(...) method. The default code is responsible for painting the background.
but it gets smeared down the path
Same answer as above. You need the background to be painted so that all the old paintings are removed.
the ball is not exactly going at the bottom edge
If you mean the ball is not on an exact diagonal on the panel, that is because you set the size of the frame manually and you did not account for the size of the titlebar and borders. If you want the panel to be (475, 475) then override the getPreferredSize() method of the panel to return that dimension. Then in your code you replace the frame.setSize() with frame.pack().

Related

Java repaint() method issue. Repainting JPanel will disappear painted components in JFrame

I created a separate class for JFrame and JPanel, then draw (fillOval in a JFrame class) and draw (fillOval in a JPanel class), and a button that will just animate the JPanel components. But the problem is, whenever i repaint the JPanel class; ---- The JFrame components disappeared. I don't understand why is this happening. I want the JFrame component be permanent for every animation done in JPanel class.
Sample Code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TryRepaintIssue extends JFrame
{
public TryRepaintIssue(){
thePanel panel = new thePanel();
add(panel);
setSize(1000,1000);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void paint(Graphics g){
super.paint(g);
g.fillOval(100,500,100,100);
}
public static void main(String[] args){
new TryRepaintIssue();
}
public static class thePanel extends JPanel{
private int y = 100, vector = 1;
public thePanel(){
JButton button = new JButton("Play");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
y += vector;
repaint();
}
});
add(button);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillOval(100,y,100,100);
}
}
}
The JFrame components disappeared.
The components do not disappear. The button and panel are still displayed.
I assume you mean the custom painting of the black circle disappears.
I don't understand why is this happening
The paint() method of the frame is responsible for painting all the child components of the frame. So it repaints the JPanel you add to the frame, which in turn paints the JButton you add to the panel.
It then paints the black circle on top of the panel.
When you click the button you repaint only the "panel" which causes the JButton and the red circle to be painted.
You lose the painting of the black circle because you no longer invoke the code to paint that circle.
If you want the black circle to remain you have a couple of options:
The best solution is to NOT override paint() on the frame. Instead do all the custom painting in your panel. So paint both the black and red circles.
repaint the entire frame in your ActionListener code:
//repaint();
SwingUtilities.windowForComponent(button).repaint();
use the Glass Pane as suggested in the answer by Tom.
You shouldn't override JFrame.paint, particularly without calling the super. Usually drawing in these situations is done on a glass pane.

Updating the position of a rectangle or any other image without overlapping in JPanel

My approach was to use 'reprint()' inside a loop was successful to move an image that means updating its position without overlapping but now I want to see the image moving and I used 'thread.sleep()' to give time gaps between the repaint()s but it doesn't seem to work
import java.awt.*;
import javax.swing.*;
public class jp extends JPanel implements ActionListener{
Timer t;
JPanel jl=new JPanel();
int x,y;
jp(){
x=10;
//y=10;
t=new Timer(5,this);
t.start();
}
public void actionPerformed(ActionEvent e){
x++;
y++;
if(x>500){
x=0;
y=0;
}
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
this.setBackground(Color.black);
g.setColor(Color.blue);
g.fillRect(x,20,50,50);
}
}
public class Jpanel extends JFrame{
public static void main(String[] args) {
jp p=new jp();
JFrame j=new JFrame("TEST_CASE-1");
j.add(p);
j.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
j.setSize(700,500);
j.setVisible(true);
}
}
If you are hoping to get an animation by having multiple drawings in paintComponent() such as using a for-loop like what you did, you are going to be disappointed.
It will only perform all the drawings and show it to you all at once. Instead of concurrently drawing 200 rectangles, you only need to draw 1 rectangle and update its position.
To perform an animation, you need either a timer (You may try javax.swing.timer) or a loop if you are planning to do something more sophisticated. You can implement an infinite loop and perform the rendering in the loop while updating the position of your rectangle.
Ultimately, no matter which approach you adopt. You will need store the position of your rectangle. Update and render it in such as way:
update position
render
update position
render
update position
render
and not
update position
update position
update position
render
render
render
Example of animation with timer:
Repainting/refreshing the JFrame
Example animating using loop:
while(running){
update(); //update position of your rect
repaint(); //redraw your rect (and all other stuff)
}

Java swing.JFrame only paints content on resize of window

I have the following program. It is supposed to print red text on a green ground. When the program opens, I only see the green background but not the red text on it. Once the window is resized and by this recalculated, the red text appears.
It works properly if I use a JPanel within the window and add a component there. If the colors are set in paintComponent then, everything works fine.
So where is the problem, if I draw on the JFrame directly. Am I missing a first "update" or something? It looks like there is some information missing on first draw of the window (the additional text) of which the program only becomes aware once the window is recalculated and redrawn.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class PaintAWT extends JFrame {
PaintAWT() {
this.setSize(600, 400);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
#Override
public void paint(Graphics g) {
super.paint(g);
// Set background color:
// If you don't paint the background, one can see the red text.
// If I use setBackground I only see a green window, until it is
// resized, then the red text appears on green ground
this.getContentPane().setBackground(new Color(0,255,0));
// Set color of text
g.setColor(new Color(255,0,0));
// Paint string
g.drawString("Test", 50, 50);
}
public static void main(String[] args) {
new PaintAWT();
}
}
You should NOT be setting properties of a component in a painting method. Painting methods are for painting only. Don't use setBackground().
You should be setting the background of the content pane when you create the frame.
Whenever you do custom painting you should also be overriding the getPreferredSize() method to return the size of the component.
You should also not be extending JFrame. You only extend a class when you add functionality to the class.
Start by reading the section from the Swing tutorial on Custom Painting fore more information and working examples. The examples will show you how to better structure your code to follow Swing conventions.
You should move setBackground to constructor. Setting of background in paint method is a bad approach.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class PaintAWT extends JFrame {
PaintAWT() {
this.setSize(600, 400);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set background color:
// If you don't paint the background, one can see the red text.
// If I use setBackground I only see a green window, until it is
// resized, then the red text appears on green ground
this.getContentPane().setBackground(new Color(0,255,0));
this.setVisible(true);
}
#Override
public void paint(Graphics g) {
super.paint(g);
// Set color of text
g.setColor(new Color(255,0,0));
// Paint string
g.drawString("Test", 50, 50);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAWT();
}
});
}
}

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.

Trouble with ActionListener and object positioning

The following code draws a rectangle whenever I click on the button, I want the rectangle to be drawn only once regardless of how many times the button is clicked. Also how can I position the Rectangle in the center of the frame and the button above it?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.geom.*;
class rectangle{
public static void main(String args[]){
EventQueue.invokeLater(new Runnable(){
public void run(){
final JFrame frame=new JFrame("RECTANGLE");
final JPanel panel=new JPanel();
JButton button=new JButton("DRAW");
panel.add(button);
frame.add(panel);
frame.setSize(400,400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent event){
panel.add(new drawrectangle());
}
});
}
});
}
}
class drawrectangle extends JComponent{
public void paintComponent(Graphics g){
Graphics2D g2=(Graphics2D) g;
g2.setPaint(Color.BLACK);
Rectangle2D rect=new Rectangle2D.Double(50,50,200,200);
g2.draw(rect);
g2.fill(rect);
}
}
You can declare field
boolean firstClick = true;
Than write something like that:
public void actionPerformed(ActionEvent event){
if(firstClick){
panel.add(new drawrectangle()); }
firstClick = false;
}
To answer your first question, you can keep track of how many times the button has been pressed using a variable and incrementing it in actionPerformed() method. Then you will know how many times the button has been pressed and take action accordingly.
To answer your second question, the easier way to do it would be to use BorderLayout. Create two JPanels add button to one panel and add it to NORTH and add the second panel to CENTRE. Then when you press the button you can add the rectangle to the panel in the CENTRE.
The difficult approach but more precise way of doing it is to manually place the button and rectangle by removing the layout manager (panel.setLayout(null)), and then specifying the size and location of all components. You also will then have to keep track of change in window state etc.
Still another way is to use GridBagLayout, which is a great balance between the first and second approach.
The following code draws a rectangle whenever I click on the button, I want the rectangle to be drawn only once regardless of how many times the button is clicked. Also how can I position the Rectangle in the center of the frame and the button above it?
No the code you posted dont draw the rectangle at all when you click the button.
Just count how often your button got clicked and if it is the first time you add your button to the JPanel.
You can use BorderLayout to position the Rectangle in the center and the button above it.

Categories

Resources