Java Swing multithread management with observer pattern - java

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

Related

PaintComponent doesn't draw BufferedImage grid on JPanel

I created a 600 x 600 grid for a TicTacToe game, but every time I try to get the image(Using the getImg() method), it shows a white screen. The JPanel's background is darkGray, it neither draws the grid, nor does it show the JPanel background.
This is the code, I haven't tried anything
PanelManager.java
package TicTacToe.display.panel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class PanelManager extends JPanel implements Runnable{
Thread t;
BufferedImage grid;
public PanelManager() {
this.setPreferredSize(new Dimension(600,600));
this.setBackground(Color.darkGray);
this.setDoubleBuffered(true);
this.startThread();
}
public void getImg() {
try {
grid = ImageIO.read(getClass().getResourceAsStream("/TicTacToe/grid/grid.png"));
}
catch (IOException e) {
e.printStackTrace();
}
}
public void startThread() {
t = new Thread(this);
t.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(grid,300,300,600,600,null);
g2.dispose();
}
public void update() {
}
public void run() {
while(t != null) {
update();
try {
Thread.sleep(10);
}
catch (Exception e) {}
repaint();
}
}
}
FrameManager.java
package TicTacToe.display;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import TicTacToe.display.panel.PanelManager;
public class FrameManager {
public static void main(String[] args) {
JFrame f = new JFrame();
PanelManager pm = new PanelManager();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setTitle("TicTacToe Windowed Edition");
f.setResizable(false);
f.setVisible(true);
f.setSize(600,600);
f.setLocationRelativeTo(null);
f.add(pm);
}
}
After a bit of reading the code, I realized I didn't use the getImg(); method, all I had to do was add one line of code in the PanelManager() constructor as shown below :-
public PanelManager() {
this.setPreferredSize(new Dimension(600,600));
this.setBackground(Color.darkGray);
this.setDoubleBuffered(true);
this.getImg();
this.startThread();
}

Redrawing frame using JPanel

I am beginner with java; I have recently moved from another programming language and I only know basics.
I have a problem with redrawing using JPanel. I can draw text when I create new object, but I have problems redrawing it. Function tick() is called in short intervals by Main class and is supposed to redraw JPanel.
package main;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel{
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawString("Hello World! "+Main.integer, 50, 50);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
class Display extends JFrame {
public Display() {
Surface surface = new Surface();
setTitle("salami");
setSize(400, 300);
}
public void tick() {
surface.redraw();
}
}
Actually, you redraw the JPanel.
Here's the simplest example I have of a redrawn JPanel. It's a clock that's redrawn every 200 milliseconds.
You create the GUI first, then use a Runnable in a Thread or a Swing Timer to update the GUI periodically.
And here's the code.
package com.ggl.testing;
import java.awt.Color;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class SimpleClock implements Runnable {
private JFrame frame;
private JPanel panel;
private JTextField clockDisplay;
private Timer timer;
#Override
public void run() {
frame = new JFrame("Clock");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
createClockPanel();
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(this);
new Thread(timer).start();
}
private void createClockPanel() {
panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 6));
clockDisplay = new JTextField(12);
clockDisplay.setEditable(false);
clockDisplay.setHorizontalAlignment(JTextField.CENTER);
panel.add(clockDisplay);
}
public void exitProcedure() {
timer.setRunning(false);
frame.dispose();
System.exit(0);
}
public void setText(String text) {
clockDisplay.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleClock());
}
public class Timer implements Runnable {
private volatile boolean running;
private SimpleClock clock;
private SimpleDateFormat timeFormat;
public Timer(SimpleClock clock) {
this.clock = clock;
this.running = true;
this.timeFormat = new SimpleDateFormat("h:mm:ss a");
}
#Override
public void run() {
while (running) {
displayTime();
sleep();
}
}
public void displayTime() {
final Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
final String s = timeFormat.format(date);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
clock.setText(s);
}
});
}
public void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
}
You need to add your extened JPanel surface to your JFrame
public Display() {
Surface surface = new Surface();
add(surface);
setTitle("salami");
setSize(400, 300);
}
If this does not work post a SSCCE

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);
}
});
}
}
}

JPanel Black screen because of componentResize and long repaint command

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

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);
}
}

Categories