JPanel Black screen because of componentResize and long repaint command - java

The problem is obvious: I have a big paint command with a very large for loop and a componentResized that runs the paint command a huge amount of time, which makes my JPanel a black screen and unable to exit even when I press the x button(I must terminate it on eclipse), represented here:
package Testing;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class DrawTest extends JPanel implements ComponentListener {
public DrawTest(){
this.addComponentListener(this);
}
#Override
public void paint(Graphics g) {
int gridsize=8;
double width=getWidth()/gridsize;
for(double i=0;i<=getWidth();i+=width){
System.out.println("1");
g.drawLine((int)i,0,(int)i,getHeight());
}
double height=getHeight()/gridsize;
for(double i=0;i<=getHeight();i+=height){
System.out.println("2");
g.drawLine(0,(int)i,getWidth(),(int)i);
}
}
public static void main(String[] args){
JFrame frame=new JFrame("Fill all the squares with Ls");
DrawTest FillSquare=new DrawTest();
frame.add(FillSquare);
frame.setExtendedState( frame.getExtendedState()|frame.MAXIMIZED_BOTH );
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
#Override
public void componentHidden(ComponentEvent e) {
// TODO Auto-generated method stub
}
#Override
public void componentMoved(ComponentEvent e) {
// TODO Auto-generated method stub
}
#Override
public void componentResized(ComponentEvent e) {
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//...Perform a task...
System.out.println("Reading SMTP Info.");
}
};
Timer timer = new Timer(100 ,taskPerformer);
timer.setRepeats(false);
timer.start();
repaint();
}
#Override
public void componentShown(ComponentEvent e) {
// TODO Auto-generated method stub
}
}
I googled the problem and looked it up on stackoverthrow, and what came up was to use the Timer which prevent the componentResize to run a crazy amount of times when the user resizes the screen, but after implementing it, as shown in the code, it still doesn't seem to be working. I don't know if I did it wrong or what, it is my first time using it. After playing around and testing with the System.out.println();, I seem to run into another problem. The paint command's second loop seem to be going infinitely after the user moves the frame.
Thanks in advance!

Four things:
Don't do for(int i=1;i<=100000;i++){ inside the paint method. Paint methods should return as fast as possible, otherwise, you end up with issues like you have right now. Consider buffering the output so you don't have update it repeatedly and waste time.
Call super.paint before doing any custom painting
Override paintComponent instead of paint (and call super.paintComponent before doing any custom painting)
Every-time componentResized is called, you're creating another Timer, so if it get's called 100 times, you're creating a 100 Timers, this won't scale well
As a possible solution, you don't "need" to use ComponentListener, I tend to just override invalidate, but you will get similar results using either.
You should use a BufferedImage to represent what you want painted, this way, if the paint method is called for some reason other then the component been resized, you can just paint the BufferedImage and not worry about having to recreate the entire state from scratch.
You only need a single Timer, which is restarted each time you are notified that the the component has changed.
When the Timer is triggers the ActionListener, you would invalidate the current buffer and regenerate it. The example I've provided uses a SwingWorker to off load the work to a back ground thread, which will allow the UI to remain responsive while the buffer is updated it. You could display a little message in the paintComponent method when the buffer is null stating that the output is been regenerated, as an idea
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawTest extends JPanel {
private Timer resizeTimer;
private BufferedImage buffer;
private SwingWorker<BufferedImage, BufferedImage> bufferGenerator;
public DrawTest() {
resizeTimer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buffer = null;
if (bufferGenerator != null) {
bufferGenerator.cancel(true);
}
// Create buffer in background
bufferGenerator = new BufferGeneratorWorker();
bufferGenerator.execute();
}
});
resizeTimer.setRepeats(false);
}
#Override
public void invalidate() {
super.invalidate();
resizeTimer.restart();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (buffer != null) {
g.drawImage(buffer, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected class BufferGeneratorWorker extends SwingWorker<BufferedImage, BufferedImage> {
#Override
protected BufferedImage doInBackground() throws Exception {
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.RED);
System.out.println("-- Started");
int i = 0;
while (i < 100000 && !Thread.currentThread().isInterrupted()) {
i++;
g2d.drawLine(0, i, getWidth(), i);
}
g2d.dispose();
System.out.println("-- Completed");
return img;
}
#Override
protected void done() {
try {
buffer = get();
repaint();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Fill all the squares with Ls");
DrawTest FillSquare = new DrawTest();
frame.add(FillSquare);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.dispose();
}
}
}
Take a look at Painting in AWT and Swing, Performing Custom Painting, How to use Swing Timers and Worker Threads and SwingWorker for more details

Related

Java Swing multithread management with observer pattern

My goal is to draw a rectangle and move it smoothly from left to right, using Observer pattern.
I have a Model class, which is the Observable where I put the coordinates of the rectangle, and a Display class which is the Observer and perform repaint each time the coordinates change in the Model.
The coordinates changes in the Model are made in a while loop inside a SwingWorker : at each iteration I increment the x coordinate by one, then sleep for 100 ms, then notify the Observer (the Display) which only task is to perform a repaint. As you see the repaint() method is called on the EDT like it is adviced to do.
The problem is that the move isn't smooth after about one second, the repaint frequency change and it seems that the rectangle is less and less repainted.
Here is the Model class :
import java.util.Observable;
import java.awt.EventQueue;
import javax.swing.SwingWorker;
public class Model extends Observable{
int xCoordinate;
Model(Display d){
SwingWorker<Void,Void> sw = new SwingWorker<Void,Void>(){
#Override
protected Void doInBackground() {
while(xCoordinate<600){
xCoordinate ++;
try {
Thread.sleep(100);
} catch (InterruptedException ex) {}
setChanged();
notifyObservers(xCoordinate);
}
return null;
}
};
addObserver(d);
sw.execute();
}
public static void main(String[] a){
EventQueue.invokeLater(new Runnable(){
#Override
public void run(){
Display d = new Display();
Model m = new Model(d);
d.model = m;
}
});
}
}
And here is the Display class :
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display extends JFrame implements Observer{
Model model;
int xCoordinate;
Display(){
getContentPane().add(new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(xCoordinate, 1, 50, 50);
}
});
setSize(600, 600);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
#Override
/* arg is the updated xCoordinate*/
public void update(Observable o, Object arg) {
xCoordinate = (Integer)arg;
EventQueue.invokeLater(new Runnable(){
#Override
public void run() {
repaint();
}
});
}
}
I tried other methods, for instance using Timer in the Display, but that didn't work either. The SwingWorker maybe isn't useful here because calculations made on the SwingWorker thread are easy (increment by one) but I will need it for the heavy calculations I intend to do on my project (a pool game).
I also tried debugging by looking at time between two repaints (in Display) and time between two incrementations (in Model) and it was as expected about 100 ms each.
Thanks in advance
Okay, so as an initial test, I started with a Swing Timer...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Model model = new Model();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(model));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update();
}
});
timer.setInitialDelay(1000);
timer.start();
}
});
}
public class Model extends Observable {
private int xCoordinate;
public void update() {
xCoordinate++;
setChanged();
notifyObservers(xCoordinate);
}
public int getXCoordinate() {
return xCoordinate;
}
}
public class TestPane extends JPanel implements Observer {
private Model model;
public TestPane(Model model) {
this.model = model;
model.addObserver(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g.setColor(Color.RED);
g.fillRect(model.getXCoordinate(), 1, 50, 50);
g2d.dispose();
}
#Override
public void update(Observable o, Object arg) {
System.out.println(arg);
repaint();
}
}
}
The thing I found was, you never call setChanged on the Observable, which during my testing, meant that it never called the Observers
I also did a test with a SwingWorker...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Model model = new Model();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(model));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
SwingWorker worker = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
Thread.sleep(1000);
while (true) {
model.update();
}
}
};
worker.execute();
}
});
}
public class Model extends Observable {
private int xCoordinate;
public synchronized void update() {
xCoordinate++;
setChanged();
notifyObservers(xCoordinate);
}
public synchronized int getXCoordinate() {
return xCoordinate;
}
}
public class TestPane extends JPanel implements Observer {
private Model model;
public TestPane(Model model) {
this.model = model;
model.addObserver(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g.setColor(Color.RED);
g.fillRect(model.getXCoordinate(), 1, 50, 50);
g2d.dispose();
}
#Override
public void update(Observable o, Object arg) {
System.out.println(arg);
repaint();
}
}
}
Because of the thread synchronization issues, I synchronized access to the methods to ensure that values weren't changing between updates. Because you're using an Observer, could actually pass the "new state" to the Observer, so they aren't reliant on then value of the model at the time they use them.
Okay, so the long and short of it, you need to call setChanged on the Observable once it's been updated, so that notifyObservers will actually call the Observers
As someone undoubtedly point out, this approach suffers from inaccuracies in the timing, by it's nature, both Swing Timer and Thread.sleep only guarantee a "at least" timing and may verify between each update.
If you have a variable length operation, this will also affect the time between updates. Instead, you should be calculating the time it took you to perform your operation, subtract the amount of time you want to wait, you would then use this delay to calculate how long you want to "wait" between frames. You should also be using System.nanoTime over System.currentTimeMillis as it won't suffer from the same system clock synchronization issues

How to detect mouse hover on an image drawn from paintComponent's drawImage(); method

I am new to Java and need help. I am making a GUI for an application using images made from photoshop, and want to create a menu using images which highlights when user hover mouse over them. I have tried mouseEntered(); method by getting mouse x, y co-ordinates but it's not working. Here is the code.
public class GUI extends JComponent{
public void paintComponent(Graphics g){
super.paintComponent(g);
ImageIcon exitBtnImg = new ImageIcon("src/images/userInterface/exitBtn.png");
g.drawImage(exitBtnImg.getImage(), 0, 5, this);
mouseHandler handler = new mouseHandler();
addMouseListener(handler);
}
}
public class mouseHandler implements MouseListener{
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
if((e.getX()>100&&e.getX()<300)&&(e.getY()>50&&e.getY()<196)){
repaint();
}
}
#Override
public void mouseExited(MouseEvent e) {
}
}
Don't load resources in paintComponent, your paint method should run as fast as it can
Don't add listeners within the paint methods, this will get called a lot, so you're just repeatedly adding new listeners each time your component is painted
Use a MouseMotionListener instead of a MouseListener, you want the mouseMoved event
You need some way to know where the image is painted, so you can determine if the mouse moved within it's bounds.
Have a look at How to Write a Mouse-Motion Listener and Painting in AWT and Swing for more details.
This example uses a simple Rectangle to define the location that the image is painted within, when the mouse moves within the confines of that Rectangle, a flag is set and the component is repainted, which paints a alpha based highlight effect over the image
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Example {
public static void main(String[] args) {
new Example();
}
public Example() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage img;
private Rectangle drawRectangle;
private boolean highlight = false;
public TestPane() throws IOException {
img = ImageIO.read(...);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
highlight = drawRectangle.contains(e.getPoint());
repaint();
}
});
int width = getPreferredSize().width;
int height = getPreferredSize().height;
int x = (width - img.getWidth()) / 2;
int y = (height - img.getHeight()) / 2;
drawRectangle = new Rectangle(x, y, img.getWidth(), img.getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, drawRectangle.x, drawRectangle.y, this);
if (highlight) {
g2d.setColor(Color.RED);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.fill(drawRectangle);
}
g2d.dispose();
}
}
}
Now, having said all that, you might be better off using the rollover capabilities of JButton, which will basically do the same thing.
See How to Use Buttons, Check Boxes, and Radio Buttons for more details

window crash in java game development

This is my code which would create a window that would takeover my full screen and would have a background colour of blue and would print "Hello World" in black, but when I run this application my screen freezes and the text "Hello World" comes out and after 5 seconds the thread terminates, but background colour does not changes and the screen becomes like this!
Can anyone point out what am I doing wrong?
This is my Screen class:
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Window;
import javax.swing.JFrame;
public class Screen {
private GraphicsDevice graphic_card;
public Screen() {
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
graphic_card = environment.getDefaultScreenDevice();
}
public void set_full_screen(DisplayMode mode, JFrame window) {
window.setUndecorated(true);
window.setResizable(false);
graphic_card.setFullScreenWindow(window);
if ((mode != null) && (graphic_card.isDisplayChangeSupported())) {
try {
graphic_card.setDisplayMode(mode);
} catch (Exception exception) {
// No handling of exception
}
}
}
public Window getFullScreenWindow() {
return graphic_card.getFullScreenWindow();
}
public void restore_screen() {
Window window = graphic_card.getFullScreenWindow();
if (window != null) {
window.dispose();
}
graphic_card.setFullScreenWindow(null);
}
}
And this my Mayank class:
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Mayank extends JFrame {
public static void main(String[] args) {
DisplayMode display_mode = new DisplayMode(800, 600, 16, DisplayMode.REFRESH_RATE_UNKNOWN);
Mayank mayank = new Mayank();
mayank.run(display_mode);
}
public void run(DisplayMode display_mode) {
setBackground(Color.BLUE);
setForeground(Color.BLACK);
setFont(new Font("Arial", Font.PLAIN, 24));
Screen game_screen = new Screen();
try {
game_screen.set_full_screen(display_mode, this);
try {
Thread.sleep(5000);
} catch (Exception exception) {
// No handling of exception
}
} finally {
game_screen.restore_screen();
}
}
public void paint(Graphics g) {
g.drawString("Hello World!!", 200, 200);
}
}
I don't know where you got this code, but it is so wrong.
I created a GUI that displays a blue background for 5 seconds, then changes the background to white.
Here's the GUI.
All Swing applications must start with a call to the SwingUtilities invokeLater method to put the creation and execution of Swing components on the Event Dispatch thread (EDT).
Always draw on a JPanel. Never draw directly on a JFrame.
Your timer blocked the EDT. You must put timing and update events in a separate thread. When you change the GUI from a separate thread, you must execute the invokeLater method to ensure the update of the drawing panel is done on the EDT.
Here's the code.
package com.ggl.testing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Manyak implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Manyak());
}
#Override
public void run() {
JFrame frame = new JFrame("Hello Word Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawingPanel drawingPanel = new DrawingPanel();
frame.add(drawingPanel);
frame.pack();
frame.setVisible(true);
new Thread(new Delay(drawingPanel)).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -685508126882892538L;
private Color color;
public DrawingPanel() {
this.setPreferredSize(new Dimension(400, 400));
this.color = Color.BLUE;
}
public void setColor(Color color) {
this.color = color;
this.repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.setFont(new Font("Arial", Font.PLAIN, 24));
g.drawString("Hello World!", 120, 200);
}
}
public class Delay implements Runnable {
private DrawingPanel drawingPanel;
public Delay(DrawingPanel drawingPanel) {
super();
this.drawingPanel = drawingPanel;
}
#Override
public void run() {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setColor(Color.WHITE);
}
});
}
}
}

Window Resize event

I have a program that scales an image to the size of the screen. I currently have a component listener listening for a componentResized event, but this is not what I want. I would like the method to only be called one the user lift's there finger off their mouse, not as they are doing the resizing. This way, my image will not constantly be resizing to the user's specifications.
Thanks!
A solution is to supply a Swing Timer which is reset each time componentResized is called. This injects a small delay between the last resize event and the time you should perform the resize action.
import javax.swing.Timer;
//...
// Declare an instance variable...
private Timer resizeTimer;
//...
// Probably in you classes constructor
resizeTimer = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Actually perform the resizing of the image...
resizeBackgroundImage();
}
});
// Don't want a repeating event...
resizeTimer.setRepeats(false);
//...
public void componentResized(ComponentEvent evt) {
resizeTimre.restart();
}
This basically, sets it up so that it will require 250 milliseconds between resize events before an attempt is made to resize the image. You can play around with the value to suit your own needs.
Updated with runnable example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RescaleTest {
public static void main(String[] args) {
new RescaleTest();
}
public RescaleTest() {
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 class TestPane extends JPanel {
private BufferedImage master;
private Image scaled;
private Timer resizeTimer;
public TestPane() {
try {
master = ImageIO.read(new File("/path/to/your/image"));
scaled = master;
} catch (IOException exp) {
exp.printStackTrace();
}
resizeTimer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
resizeBackground();
}
});
resizeTimer.setRepeats(false);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
resizeTimer.restart();
}
});
}
protected void resizeBackground() {
// This is not my preferred scaling process, I prefer to use
// a divide and conqure approach and do so in the background
// where possible, but this is beyond the scope of the question...
if (getWidth() < getHeight()) {
scaled = master.getScaledInstance(getWidth(), -1, Image.SCALE_SMOOTH);
} else {
scaled = master.getScaledInstance(-1, getHeight(), Image.SCALE_SMOOTH);
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return master != null ? new Dimension(master.getWidth(), master.getHeight()) : new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (scaled != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - scaled.getWidth(this)) / 2;
int y = (getHeight() - scaled.getHeight(this)) / 2;
g2d.drawImage(scaled, x, y, this);
g2d.dispose();
}
}
}
}
nb: The scaling used in this example is not my preferred method and was done for demonstration purposes only. See The Perils of Image.getScaledInstance() for details and Scale the ImageIcon automatically to label size for an alterantive approach...
If you put Toolkit.getDefaultToolkit().setDynamicLayout(false); right inside of main it will disable the frame from updating dynamically as you increase/decrease it's size. The ui will only be updated after you stop resizing.
import MainMenu.GameManager;
import java.awt.*;
import java.io.IOException;
public class Main {
Main(){
}
public static void main(String[] args) throws IOException {
GameManager manager = new GameManager();
Toolkit.getDefaultToolkit().setDynamicLayout(false);
}
}

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