How does repaint() method behave inside an infinite for loop? - java

I have this code,
import java.awt.*;
import java.applet.*;
public class FirstApplet extends Applet
{
int len;
char ch;
String msg="Hello World ";
public void init()
{
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start()
{
System.out.println("Inside Start");
repaint();
}
public void paint(Graphics g)
{
System.out.println("Inside paint");
g.drawString(msg,200,200);
}
}
It outputs a CYAN coloured background with Hello World on it.And on the console(cmd),it outputs-
Inside Start
Inside paint
Now if I modify the code to this-
import java.awt.*;
import java.applet.*;
public class FirstApplet extends Applet
{
String msg="Hello World ";
int len;
char ch;
public void init()
{
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start()
{
System.out.println("Inside Start");
for(;;)
{
repaint();
}
}
public void paint(Graphics g)
{
System.out.println("Inside paint");
g.drawString(msg,200,200);
}
}
It outputs a white coloured screen with no text on it,and on the console it just outputs-
Inside Start
I am unable to understand the output of second program,Although I am calling the repaint() inside the for loop every time yet why the colour of the applet window is not changing to CYAN colour and why its not printing "Inside paint" on the console?Can somebody please help me out.

You're tying up the GUI's event thread with your infinite loop, so that although repaint() is being called, the GUI's event thread is unable to act on it. Consider using a Swing Timer or a background thread instead.
For example, and continuing with your 1890's Applet example:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.*;
public class PaintEg extends Applet {
String msg = "Hello World ";
int len;
char ch;
public void init() {
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start() {
System.out.println("Inside Start");
new Thread(new Runnable() {
public void run() {
for (;;) {
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public void paint(Graphics g) {
System.out.println("Inside paint");
g.drawString(msg, 10, 20);
}
}
Better maybe is a Swing example that uses a Swing Timer and performs basic animation:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class PaintEg2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 30;
private String msg = "Hello World ";
private int msgX = 0;
private int msgY = 0;
public PaintEg2() {
setBackground(Color.CYAN);
setForeground(Color.WHITE);
setFont(new Font(Font.SANS_SERIF, Font.BOLD, 20));
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString(msg, msgX, msgY);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
msgX++;
msgY++;
repaint();
}
}
private static void createAndShowGui() {
PaintEg2 mainPanel = new PaintEg2();
JFrame frame = new JFrame("PaintEg2");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Note that in your code repaint() is being called and is being executed, but the paint manager is unable to act on this because it does so on the GUI's event thread. If the GUI's event thread is tied up, no painting can be done.
For more on how painting is done in Swing and AWT, please read Painting in AWT and Swing

Related

Using repaint() to draw on JPanel which is added to JFrame

Attempting to draw my rectangle across the screen horizontally in realtime. When I run this I get nothing but the JFrame. I'm not sure what I am missing aside from maybe some type of threading freeze to redraw the shape maybe?
public class ScreenTest extends JFrame {
int rectY = 50;
public ScreenTest()
{
setSize(300,200);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private class DrawPanel extends JPanel {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(80, rectY, 50, 50);
}
}
public void Draw()
{
DrawPanel test = new DrawPanel();
add(test);
while (rectY < 200)
{
rectY = rectY + 10;
test.repaint();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ScreenTest myWindow = new ScreenTest();
myWindow.Draw();
}
}
Swing is single threaded and not thread safe.
What this means is, you should not perform any kind of long running or blocking operations within the "Event Dispatching Thread", as this will stop the UI from been painted or responding to new events.
It also means you should not update the UI, or any state the UI relies on, from outside the context of the Event Dispatching Thread.
Your code "is" working, but because the while-loop can run so fast, it's completing before the window is realised on the screen (visible and updatable). Swing is also optimised, so all the repaint calls are likely been consolidated into a single repaint pass.
A better solution might to start with Swing `Timer, which acts as a pseudo repeating loop, but which is called on within the context of the Event Dispatching Thread.
Start by taking a look at Concurrency in Swing and How to Use Swing Timers for more details.
Runnable Example
import java.awt.Color;
import java.awt.EventQueue;
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;
public class ScreenTest extends JFrame {
public ScreenTest() {
setSize(300, 200);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private class DrawPanel extends JPanel {
int rectY = 50;
private Timer timer;
// This is just convince
#Override
public void addNotify() {
super.addNotify();
timer = new Timer(25, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
rectY += 1;
repaint();
}
});
// Otherwise it disappears to fast
timer.setInitialDelay(1000);
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
timer = null;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(80, rectY, 50, 50);
}
}
public void Draw() {
DrawPanel test = new DrawPanel();
add(test);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ScreenTest myWindow = new ScreenTest();
myWindow.Draw();
}
});
}
}
it is working but is so fast that you can't see it, you need to make the loop which changes the Y coordinate slower with a delay. to solve it i used Thread.sleep() in the while loop:
package paquete;
import javax.swing.*;
import java.awt.*;
public class ScreenTest extends JFrame {
int rectY = 50;
public ScreenTest()
{
setSize(300,200);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private class DrawPanel extends JPanel {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(80, rectY, 50, 50);
}
}
public void Draw() throws InterruptedException {
DrawPanel test = new DrawPanel();
add(test);
while (rectY < 200)
{
rectY = rectY + 10;
Thread.sleep(100);
test.repaint();
}
}
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ScreenTest myWindow = new ScreenTest();
myWindow.Draw();
}
}
i hope this helps you, you can change the duration changing the number inside the argument of Thread.sleep()

Repainting-Thread doesn't repaint Inner-Class JPanel

I want to make a little rain program in swing, but for some reason I cannot repaint the panel from another class. I tried using an inner class for the panel this time, but it doesn't seem to work with repainting it from another class/thread. Does someone know why?
sscce:
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class UI extends JFrame {
public static void main(String[] args) {
UI myProgram = new UI();
myProgram.setVisible(true);
}
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth()/2, this.getHeight()/2, 50,50);
}
}
}
UI Class (with inner class JPanel):
package Rain;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class UI extends JFrame {
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
private class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
private ArrayList<Raindrop> rain = new ArrayList<Raindrop>();
private static final int AMOUNT = 50;
private Random rand = new Random();
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
for(int i = 0; i < AMOUNT; i++) {
createRain();
}
new Painter(this);
time.start();
}
public void createRain() {
float distance = rand.nextFloat() * 90 + 10;
int x = rand.nextInt(this.getWidth());
int y = 100;
rain.add(new Raindrop(distance,x,y));
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
}
public void paintComponent(Graphics g) {
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
}
}
Painter:
package Rain;
import javax.swing.JPanel;
public class Painter extends Thread {
private JPanel p;
public Painter(JPanel p) {
this.p = p;
this.start();
}
public void run() {
while(true) {
System.out.println("trying to paint..");
p.repaint();
}
}
}
Console Output:
trying to paint..
tick
trying to paint..
tick
...
Expected Output:
trying to paint..
painting
tick
trying to paint..
...
The thread does work but it never calls the paintComponent(Graphics g) function in the panel
All Swing applications must run on their own thread, called EDT. (Hopefully, you start your application by calling SwingUtilities#invokelater method). So, repainting a component outside of Event Dispatch Thread is really bad bad (bad) idea. Instead of creating new Thread, repaint the component inside javax.swing.Timer's action listener since it will run in EDT.
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
repaint(); //repaint in EDT
}
Also, when you #Override paintComponent method, always start by calling super.paintComponent(g);
public void paintComponent(Graphics g) {
super.paintComponent(g);//let component get painted normally
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
UPDATE after your SSCCE
In order a component to get painted, it must have a parent. You UserPanel p = new UserPanel(this); but you never add it to the frame:
UserPanel p = new UserPanel(this);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p);
The complete SSCCE:
public class UI extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { //Run in EDT
UI myProgram = new UI();
myProgram.setVisible(true);
});
}
public UI() {
super("title");//call super for frame
this.setSize(new Dimension(500, 300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
//Use border layout to make p fit the whole frame
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p, BorderLayout.CENTER);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth() / 2, this.getHeight() / 2, 50, 50);
}
}
}
Don't ignore the SwingUtilities.invokeLater.

i can't see circle moving

While using Swing in java, I am trying to move a circle slowly from a starting position to an end position when clicking a button. However, I can't see the circle moving. It just moves from start to end in an instant.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp {
private int x = 10;
private int y = 10;
private JFrame f;
private MyDraw m;
private JButton b;
public void go() {
f = new JFrame("Moving circle");
b = new JButton("click me to move circle");
m = new MyDraw();
f.add(BorderLayout.SOUTH, b);
f.add(BorderLayout.CENTER, m);
f.setSize(500, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
b.addActionListener(new Bute());
}
public static void main(String[] args) {
MyApp m = new MyApp();
m.go();
}
private class Bute implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 150; i++) {
++x;
++y;
m.repaint();
Thread.sleep(50);
}
}
}
private class MyDraw extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, 500, 500);
g.setColor(Color.red);
g.fillOval(x, y, 40, 40);
}
}
}
I think the problem is with the action listener because when I'm doing it without using button it is working. Any suggestions?
As Andrew Thompson said, calling Thread.sleep() without defining a second thread freezes everything, so the solution is to define and run another thread like so:
class Bute implements ActionListener, Runnable {
//let class implement Runnable interface
Thread t; // define 2nd thread
public void actionPerformed(ActionEvent e) {
t = new Thread(this); //start a new thread
t.start();
}
#Override //override our thread's run() method to do what we want
public void run() { //this is after some java-internal init stuff called by start()
//b.setEnabled(false);
for (int i = 0; i < 150; i++) {
x++;
y++;
m.repaint();
try {
Thread.sleep(50); //let the 2nd thread sleep
} catch (InterruptedException iEx) {
iEx.printStackTrace();
}
}
//b.setEnabled(true);
}
}
The only problem with this solution is that pressing the button multiple times will speed up the circle, but this can be fixed by making the button unclickable during the animation via b.setEnabled(true/false). Not the best solution but it works.
As said in the comments and another answer, don't block the EDT. Thead.sleep(...) will block it, so you have two options:
Create and manage your own (new) thread.
Use a Swing Timer
In this answer I'll be using a Swing Timer, since it's easier to use. I also changed the paintComponent method to use the Shape API and change the button text to start and stop accordingly as well as reusing the same ActionListener for the button and the timer:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingCircle {
private JFrame frame;
private CustomCircle circle;
private Timer timer;
private JButton button;
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingCircle()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(this.getClass().getSimpleName());
circle = new CustomCircle(Color.RED);
timer = new Timer(100, listener);
button = new JButton("Start");
button.addActionListener(listener);
circle.setBackground(Color.WHITE);
frame.add(circle);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private ActionListener listener = (e -> {
if (!timer.isRunning()) {
timer.start();
button.setText("Stop");
} else {
if (e.getSource().equals(button)) {
timer.stop();
button.setText("Start");
}
}
circle.move(1, 1);
});
#SuppressWarnings("serial")
class CustomCircle extends JPanel {
private Color color;
private int circleX;
private int circleY;
public CustomCircle(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
g2d.fill(new Ellipse2D.Double(circleX, circleY, 50, 50));
}
#Override
public Dimension preferredSize() {
return new Dimension(100, 100);
}
public void move(int xGap, int yGap) {
circleX += xGap;
circleY += yGap;
revalidate();
repaint();
}
public int getCircleX() {
return circleX;
}
public void setCircleX(int circleX) {
this.circleX = circleX;
}
public int getCircleY() {
return circleY;
}
public void setCircleY(int circleY) {
this.circleY = circleY;
}
}
}
I'm sorry, I can't post a GIF as I wanted but this example runs as expected.

draw a circle when certain condition is met

My program is to convert the letters to some signals.My main method generates some random letters. The letter is passed to another method which calls repaint() method based on the Letter generated.The PaintComponent() method is used to drew a circle filled with white color.When i execute the program i get only a Jframe. I don't see the circle.Please help.
package morsecode;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.awt.*;
public class MorseCode extends Frame {
public static void main(String[] args) {
MorseCode mc = new MorseCode();
MorseCode frame = new MorseCode();
final String chars = "abcdefghijklmnopqrstuvwxyz1234567890";
char word;
for(int i=1;i<=1;i++)
{
Random rand = new Random();
int x = rand.nextInt(36);
word = chars.charAt(x);
System.out.print(word);
frame.setBackground(Color.BLACK);
frame.addWindowListener(
new WindowAdapter()
{
#Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
}
);
frame.setSize(400, 400);
frame.setVisible(true);
mc.toMorseCode(word);
}
}
void toMorseCode(char letter)
{
switch(letter)
{
case 'A' | 'a':
repaint();
Thread.sleep(1000);
repaint();
Thread.sleep(2000);
break;
case 'B' | 'b':
repaint();
Thread.sleep(1000);
repaint();
Thread.sleep(1000);
repaint();
Thread.sleep(1000);
repaint();
Thread.sleep(2000);
break; ..............
}
}
public void paintComponent(Graphics g) {
Graphics2D ga = (Graphics2D)g;
ga.setColor(Color.white);
ga.fillOval(125,125,150,150);
}
}
Two things...
First, calling Thread.sleep(2000); within the Event Dispatching Thread will prevent the EDT from processing events on the event queue, including paint events.
Second, Frame doesn't have a paintComponent.
Adding the #Override annotation and trying to call super.paintComponent would have highlighted this issue as the code wouldn't have compiled.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
First of all, start by using a JPanel to hold your core logic and perform your custom painting.
Second, use a javax.swing.Timer to perform animation. See How to use Swing Timers for more details
Updated
The basic concept is relatively simple. You need some kind of second/background thread which can generate the delays between the changes in the output. You then need to update the UI before each delay based on what type of information you are trying to display.
The implementation becomes tricky because Swing, like most GUI frameworks, is single threaded and not thread safe.
This means, you can not block the GUI thread, doing so will prevent the UI from been repainted, amongst other things and you must update the state of any UI component from within the context of the GUI thread.
This means that while you can use a Thread to run in the background, you must ensure that all changes/modifications to the UI are carried out only from within the EDT.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MorseCodeTest {
public static void main(String[] args) {
new MorseCodeTest();
}
public MorseCodeTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static final int GAP = 500;
public static final int DOT = 1000;
public static final int DASH = 4000;
public interface Transmitter {
public void setTap(boolean tap);
}
public class TestPane extends JPanel implements Transmitter {
private MorseCode code;
private boolean tapped;
public TestPane() {
code = MorseCode.create('A').addDot().addDash();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Signalar signalar = new Signalar(TestPane.this, code);
signalar.execute();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (tapped) {
Graphics2D g2d = (Graphics2D) g.create();
int diameter = Math.min(getWidth(), getHeight()) / 2;
int x = (getWidth() - diameter) / 2;
int y = (getHeight() - diameter) / 2;
g2d.fillOval(x, y, diameter, diameter);
g2d.dispose();
}
}
#Override
public void setTap(boolean tap) {
tapped = tap;
repaint();
}
}
public class Signalar extends SwingWorker<Void, Boolean> {
private final MorseCode code;
private final Transmitter transmitter;
public Signalar(Transmitter transmitter, MorseCode code) {
this.code = code;
this.transmitter = transmitter;
}
#Override
protected void process(List<Boolean> chunks) {
transmitter.setTap(chunks.get(chunks.size() - 1));
}
#Override
protected Void doInBackground() throws Exception {
for (Tone tone : code.getTones()) {
publish(true);
Thread.sleep(tone.getDelay());
publish(false);
Thread.sleep(GAP);
}
return null;
}
}
public static class Tone {
private final int delay;
public Tone(int delay) {
this.delay = delay;
}
public int getDelay() {
return delay;
}
}
public static class DashTone extends Tone {
public DashTone() {
super(DASH);
}
}
public static class DotTone extends Tone {
public DotTone() {
super(DOT);
}
}
public static class MorseCode {
private final char value;
private final List<Tone> tones;
public static MorseCode create(char value) {
MorseCode code = new MorseCode(value);
return code;
}
public MorseCode(char value) {
this.value = value;
this.tones = new ArrayList<>(25);
}
public char getValue() {
return value;
}
public MorseCode addDash() {
return addTone(new DashTone());
}
public MorseCode addDot() {
return addTone(new DotTone());
}
public MorseCode addTone(Tone tone) {
tones.add(tone);
return this;
}
public Iterable<Tone> getTones() {
return tones;
}
}
}

JButton isn't in the right place when a BufferedImage is used in it's container

I'm trying to create a simple game using AWT but I want to have some JButtons aswell to exit/reset the game. The problem is, I want the BufferedImage to be drawn inside the visible frame like so in my container I have this at the end:
g.drawImage(bf,getParent().getInsets().left,getParent().getInsets().top,null);
My problem is, when I add a JButton to that frame, it only detects rollover in space that doesn't take into account the offsetting, but is drawn in a space that does. This is the relevant code (con is the container).
private void addButtons()
{
reset = new JButton("reset");
reset.setBounds(180,460, 75,30);
reset.addActionListener( this );
con.add(reset);
exit = new JButton("exit");
exit.setBounds(290,460, 60,30);
exit.addActionListener( this );
con.add(exit);
con.repaint();
}
The paint method in the Container
public void paint(Graphics g)
{
bf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics b = bf.getGraphics();
b.setColor(Color.GRAY);
b.fillRect(0, 0, this.getWidth(), this.getHeight());
b.setColor(Color.BLACK);
b.drawRect(0,0,420,420);
super.paint(b);
g.drawImage(bf,getParent().getInsets().left,getParent().getInsets().top,null);
}
How can I make the button be drawn and detected in the same spot?
here is a screenshot of the problem
As requested:
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.Color;
public class Draw implements ActionListener{
private SnakeFrame frame;
private SnakeCon con;
JButton reset, exit;
private boolean res;
public Draw()
{
frame = new SnakeFrame("Snake");
frame.setResizable(false);
frame.setLayout(null);
frame.setSize(600,600);
frame.setVisible(true);
con = new SnakeCon();
con.setBounds(0,0,600,600);
frame.add(con);
}
private void addButtons()
{
reset = new JButton("reset");
reset.setBounds(180,460, 75,30);
reset.addActionListener( this );
con.add(reset);
exit = new JButton("exit");
exit.setBounds(290,460, 60,30);
exit.addActionListener( this );
con.add(exit);
con.repaint();
}
public void run()
{
addButtons();
res = false;
boolean dead = false;
while(!dead)
{
if( (res) )
dead = true;
if (!dead)
{
try{
Thread.sleep(100);
}
catch (Exception e)
{}
frame.repaint();
}
}
con.removeAll();
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == reset)
res = true;
else if (e.getSource() == exit)
System.exit(0);
}
}
--
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.Color;
public class SnakeCon extends Container{
private BufferedImage bf;
public SnakeCon()
{
super();
setBounds(0,0,600,600);
}
public void paint(Graphics g)
{
bf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics b = bf.getGraphics();
b.setColor(Color.GRAY);
b.fillRect(0, 0, this.getWidth(), this.getHeight());
b.setColor(Color.BLACK);
b.drawRect(0,0,420,420);
super.paint(b);
g.drawImage(bf,getParent().getInsets().left,getParent().getInsets().top,null);
}
public void update(Graphics g)
{
paint(g);
}
}
--
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.Color;
public class SnakeFrame extends Frame implements WindowListener{
private BufferedImage bf;
public SnakeFrame(String s)
{
super(s);
addWindowListener( this );
}
public void paint(Graphics g)
{
bf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics b = bf.getGraphics();
super.paint(b);
g.drawImage(bf,0,0,null);
}
public void update(Graphics g)
{
paint(g);
}
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
public void windowClosed(WindowEvent e) { }
public void windowOpened(WindowEvent e) { }
public void windowIconified(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowActivated(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) { }
}
--
public class Main {
public static void main(String[] args)
{
boolean never = false;
Draw d = new Draw();
while(!never)
{
d.run();
}
System.exit(0);
}
}
Im not sure exactly what is wrong/what you want (why do you have a loop to constantly drawing buttons and invoking removeAll()? etc but i cant shake the feeling it might be implemented in a more readable/efficient way)...
But here are some suggestions that can only help your code get better:
Dont use null/Absolute Layout choose an appropriate LayoutManager.
Do not override JFrame paint(..), rather add JPanel to JFrame and override paintComponent(Graphics g) of JPanel and do drawing there.(Do not forget to have super.paintComponent(..) as 1st call in overriden paintComponent method. See here for more: Performing Custom Painting
Do not set JFrame visible before adding all components to JFrame
Always create and manipulate Swing components on Event Dispatch Thread like so:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//create Swing components
}
});
Do not do long running tasks on Event Dispatch Thread rather use Swing Timer/Swing Worker
Do not call setSize(..) on JFrame rather override getPreferredSize() of JPanel and return Dimensions which fit all components (see here for reasoning), than call pack() on JFrame before setting it visible
Dont extend JFrame unnecessarily or Container!
Adding WindowListener for detecting JFrame exit is not worth the lines rather use:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
to exit Swing application when X is pressed.

Categories