I am trying to make a thread that reads the screen and displays it in a frame, this code is meant to run at 5fps, so far it reads the screen, but I am having trouble making the JFrame display the updating Image each "frame" or 200 mili-seconds. when I use repaint(); or revalidate();
public static void startScreenRecorder()
{
Thread screenThread = new Thread()
{
public synchronized void run()
{
long time;
long lastFrameTime = 0;
JFrame frame = new JFrame("Screen capture");
ImagePanel panel = new ImagePanel(captureScreen());
frame.add(panel);
frame.setSize(300, 400);
frame.setVisible(true);
while (true)
{
time = System.currentTimeMillis();
while (time - lastFrameTime < 190)
{
try {
Thread.sleep(10);
} catch (Exception e) {
}
time = System.currentTimeMillis();
}
lastFrameTime = time;
panel = new ImagePanel(captureScreen());
panel.revalidate();
panel.repaint();
frame.revalidate();
frame.repaint();
}
}
};
screenThread.start();
}
Don't use Thread.sleep() to attempt to control animation.
Animation should be done by using a Swing Timer. When you use a Timer the GUI is automatically updated on the EDT.
panel = new ImagePanel(captureScreen());
The above code doesn't do anything. It just creates a panel in memory. Nowhere to you actually add the panel to the GUI. Changing the reference of a variable does not update the GUI.
Instead you should probably add a JLabel to the frame (when you initially create the frame). Then when you have a new Image you just do:
label.setIcon( new ImageIcon( your screen capture ) );
I wouldn't be surprised if your code shows no images at all since it ignores Swing threading rules:
All Swing code needs to be called on the Swing event dispatch thread (EDT) only.
All other long-running code needs to be called in a background thread. I assume that this means captureScreen().
You should never call Thread.sleep(...) on the Swing event thread unless you want to put your entire application to sleep.
Better perhaps to use a Swing Timer.
You create new ImagePanels but do nothing with them -- you never add them to the GUI for instance, except for the first JPanel. Note that if you change the object a variable refers to, here the panel variable, this will have absolutely no effect on instances of the object used elsewhere, there the JPanel displayed in the GUI.
Rather than create new JPanels, why not instead create ImageIcons with your images and swap a visualized JLabel's Icon with setIcon(...)?
Since you have a lot of background stuff going on, consider using a SwingWorker<Void, Icon> to do your work, and have it publish ImageIcons that are then displayed in the GUI's JLabel. If you did this, then you probably wouldn't use a Swing Timer since the timing would be done in the SwingWorker's background thread.
For example:
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
#SuppressWarnings("serial")
public class SwingWorkerEg extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private JLabel displayedLabel = new JLabel();
public SwingWorkerEg() {
setLayout(new BorderLayout());
add(displayedLabel);
try {
MySwingWorker mySwingWorker = new MySwingWorker();
mySwingWorker.execute();
} catch (AWTException e) {
e.printStackTrace();
}
}
public void setLabelIcon(Icon icon) {
displayedLabel.setIcon(icon);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private class MySwingWorker extends SwingWorker<Void, Icon> {
private final Rectangle SCREEN_RECT = new Rectangle(0, 0, PREF_W,
PREF_H);
private Robot robot = null;
public MySwingWorker() throws AWTException {
robot = new Robot();
}
#Override
protected Void doInBackground() throws Exception {
Timer utilTimer = new Timer();
TimerTask task = new TimerTask() {
#Override
public void run() {
BufferedImage capturedImage = captureScreen();
publish(new ImageIcon(capturedImage));
}
};
long delay = 200;
utilTimer.scheduleAtFixedRate(task, delay, delay);
return null;
}
#Override
protected void process(List<Icon> chunks) {
for (Icon icon : chunks) {
setLabelIcon(icon);
}
}
private BufferedImage captureScreen() {
BufferedImage img = robot.createScreenCapture(SCREEN_RECT);
return img;
}
}
private static void createAndShowGui() {
SwingWorkerEg mainPanel = new SwingWorkerEg();
JFrame frame = new JFrame("SwingWorker Eg");
frame.setDefaultCloseOperation(JFrame.EXIT_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();
}
});
}
}
Which would display...
Related
Hello first of all when I run the program a button appear , when I press the button the image will go from top to down.
I try the code when the image go from top to down , it work very well
BUT when I put all the codes together there is an error in ( frame.add(new AnimationPane() ); )
Question : How to add AnimationPane() to the frame ???
because this is my problem.
The idea that I want to make two scenes , the first one have a button to make go to the second scene which will have an image (it must be pushed from top until reach down ).
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package maincontentpaneswitching;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MainContentPaneSwitching {
private static class ChangeContentPaneListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// I want to put the image here
JPanel newFrameContents = new JPanel(); //Uses FlowLayout by default.
newFrameContents.add(new JLabel("You have successfully changed the content pane of the frame!", JLabel.CENTER));
/*We assume that the source is a JButton and that the Window is of type JFrame, hence
the following utility method call is possible without letting any errors appear:*/
JFrame frame = (JFrame) SwingUtilities.getWindowAncestor((JButton) e.getSource());
frame.setSize(600, 300);
frame.setContentPane(newFrameContents); //Change the content pane of the frame.
frame.revalidate(); //Notify the frame that the component hierarchy has changed.
frame.add(new AnimationPane() );
frame.pack(); //Resize the frame as necessary in order to fit as many contents as possible in the screen.
frame.setLocationRelativeTo(null); //Place the frame in the center of the screen. As you can tell, this needs its size to calculate the location, so we made sure in the previous line of code that it is set.
frame.repaint(); //Repaint frame with all its contents.
}
}
public class AnimationPane extends JPanel {
private BufferedImage boat;
private int yPos = 0;
private int direction = 1;
public AnimationPane() {
try {
boat = ImageIO.read(new URL("https://i.stack.imgur.com/memI0.png"));
Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yPos += direction;
if (yPos + boat.getHeight() > getHeight()) {
yPos = getHeight() - boat.getHeight();
direction *= +1;
} else if (yPos < 0) {
yPos = 0;
direction *= +1;
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return boat == null ? super.getPreferredSize() : new Dimension(boat.getHeight()*2 , boat.getWidth() *2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = getWidth() - boat.getWidth();
g.drawImage(boat, x, yPos, this);
}
}
private static class MainRunnable implements Runnable {
#Override
public void run() {
JButton changeContentPaneButton = new JButton("Click to go to the next image!");
changeContentPaneButton.addActionListener(new ChangeContentPaneListener());
JPanel frameContents = new JPanel(); //Uses FlowLayout by default.
frameContents.add(changeContentPaneButton);
JFrame frame = new JFrame("My application");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Tells the frame that when the user closes it, it must terminate the application.
frame.setContentPane(frameContents); //Add contents to the frame.
frame.pack(); //Resize the frame as necessary in order to fit as many contents as possible in the screen.
frame.setLocationRelativeTo(null); //Place the frame in the center of the screen. As you can tell, this needs its size to calculate the location, so we made sure in the previous line of code that it is set.
frame.setVisible(true);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MainRunnable()); //Swing code must always be used in the Event Dispatch Thread.
}
}
Introduction
As I said in my comment, I couldn't get the image animation to work properly. At least this code would give you a solid foundation to start with.
Here's the GUI I came up with.
Here's the GUI after you left-click on the button.
If you're going to add comments to your code, put the comments on separate lines from the code. Not everyone has a large monitor and can read 200+ character lines of code.
Explanation
Oracle has a rad tutorial, Creating a GUI With Swing. Skip the Netbeans section.
When I create a Swing GUI, I use the model/view/controller (MVC) pattern. This pattern allows me to separate my concerns and focus on one part of the application at a time.
In Swing, the MVC pattern means:
The view reads information from the model
The view may not update the model
The controller updates the model and repaints/revalidates the view.
There's usually not one controller to "rule them all". Each listener controls its portion of the model and the view.
When I put together an application, I code one tiny tiny piece of it, then run tests. I probably ran two to three dozen tests, and this was mostly coded by you.
Model
I created a BoatImage class to read the boat image. It's a separate class, so I can read the image before I start to construct the GUI.
View
I created a JFrame. I created a main JPanel with a CardLayout.
I use a CardLayout to layout the button JPanel and the image JPanel. This way, the JFrame is not constantly changing size.
I create the JFrame and JPanels as separate methods/classes. This makes it much easier for people, including yourself, to read and understand the view code.
Controller
I coded the ChangeContentPaneListener to change from the button JPanel to the image JPanel. This is where you would put your image animation code.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I could post this code as one block.
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MainContentPaneSwitching implements Runnable {
public static void main(String[] args) {
// Swing code must always be used in the Event Dispatch Thread.
SwingUtilities.invokeLater(new MainContentPaneSwitching());
}
private AnimationPane animationPane;
private BoatImage boatImage;
private CardLayout cardLayout;
private JPanel mainPanel;
public MainContentPaneSwitching() {
this.boatImage = new BoatImage();
}
#Override
public void run() {
JFrame frame = new JFrame("My application");
// Tells the frame that when the user closes it, it
// must terminate the application.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.mainPanel = createMainPanel();
frame.add(mainPanel, BorderLayout.CENTER);
// Resize the frame as necessary in order to fit as many contents
// as possible in the screen.
frame.pack();
// Place the frame in the center of the screen. As you can tell, this
// needs its size to calculate the location, so we made sure in the
// previous line of code that it is set.
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JPanel createMainPanel() {
cardLayout = new CardLayout();
JPanel panel = new JPanel(cardLayout);
panel.add(createButtonPanel(), "button");
animationPane = new AnimationPane(boatImage);
panel.add(animationPane, "image");
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JButton changeContentPaneButton = new JButton(
"Click to go to the next image!");
changeContentPaneButton.addActionListener(
new ChangeContentPaneListener(this, boatImage));
panel.add(changeContentPaneButton);
return panel;
}
public JPanel getAnimationPane() {
return animationPane;
}
public void repaint() {
animationPane.repaint();
}
public class AnimationPane extends JPanel {
private static final long serialVersionUID = 1L;
private BoatImage boat;
public AnimationPane(BoatImage boat) {
this.boat = boat;
BufferedImage image = boat.getBoat();
this.setPreferredSize(new Dimension(image.getWidth(),
image.getHeight()));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage image = boat.getBoat();
int x = getWidth() - image.getWidth();
g.drawImage(image, x, boat.getyPos(), this);
}
}
private class ChangeContentPaneListener implements ActionListener {
private int direction, yPos;
private final MainContentPaneSwitching view;
private final BoatImage model;
public ChangeContentPaneListener(MainContentPaneSwitching view,
BoatImage model) {
this.view = view;
this.model = model;
this.direction = 1;
this.yPos = 0;
}
#Override
public void actionPerformed(ActionEvent e) {
cardLayout.show(mainPanel, "image");
}
}
public class BoatImage {
private int yPos;
private BufferedImage boat;
public BoatImage() {
try {
URL url = new URL("https://i.stack.imgur.com/memI0.png");
boat = ImageIO.read(url); // boat.jpg
} catch (MalformedURLException e) {
e.printStackTrace();
boat = null;
} catch (IOException e) {
e.printStackTrace();
boat = null;
}
this.yPos = 0;
}
public BufferedImage getBoat() {
return boat;
}
public void setyPos(int yPos) {
this.yPos = yPos;
}
public int getyPos() {
return yPos;
}
}
}
I am programming a multiplication app for very large integers, I need to update every sigle step of the multiplication in a Swing component ( I created a JPane extended class with a JTextArea in it, then add it to the JFrame inside a ScrollPane). The issue is that this Swing component only updates once the multiplication algorithm is done. I tried using a Thread that would call repaint method of the Pane every 10 ms, but it did not work. The next is a sample of the problem.
This is the main Frame class:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Frame extends JFrame implements ActionListener{
private Console console;
private JButton calculate;
private Calculator calculator;
public Frame(){
console=new Console();
calculate=new JButton("Calculate");
calculate.addActionListener(this);
calculate.setActionCommand("");
calculator=new Calculator(this);
this.setLayout(new BorderLayout());
this.add(console,BorderLayout.CENTER);
this.add(calculate, BorderLayout.NORTH);
this.setTitle("Frame");
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(new Dimension(500,500));
this.setLocationRelativeTo(null);
}
public void writeOnConsole(String txt){
console.write(txt);
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(e.getActionCommand().equals("")){
console.clear();
calculator.calculate();
}
}
public static void main(String[] args) {
new Frame();
}
}
This is the Console Class
import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
public class Console extends JPanel{
private JTextArea area;
public Console(){
this.setBorder(new TitledBorder("Console:"));
area=new JTextArea();
this.setLayout(new BorderLayout());
JScrollPane scroll=new JScrollPane(area);
this.add(scroll,BorderLayout.CENTER);
}
public void clear(){
area.setText("");
}
public void write(String txt){
area.append(txt+"\n");
}
}
Finally, this is the Calculator class (the one responsible for calling the writing)
public class Calculator {
private Frame parent;
public Calculator(Frame f){
parent=f;
}
public void calculate(){
for (int i = 0; i <= 1000000; i++) {
parent.writeOnConsole("Iteration "+i);
}
}
}
Note that if you run the program, the GUI will freeze until the Calculator class is done with the loop.
if you have a layout like a BorderLayout and you want to update it inside the JFrame do as bellow
JFrame frame = new JFrame();
BorderLayout layout = new BorderLayout();
layout.layoutContainer(frame.getContentPane());// use the frame as the border layout container
else you can use JFrame pack() method. The pack method packs the components within the window based on the component’s preferred sizes. it's not for updating but it updates the JFrame which is kind of a trick
JFrame frame = new JFrame();
//change the components dynamically
frame.pack();
or use Container methdod validate(). Validating a container means laying out its subcomponents. Layout-related changes, such as setting the bounds of a component, or adding a component to the container.
JFrame frame = new JFrame();
Container container = frame.getContentPane();
container.validate();
or if you want to update an specific component use
Component component = new JPanel();
component.repaint();
If this component is a lightweight component, repaint() method causes a call to this component's paint method as soon as possible .
or if you want for example numerous changes happen one by one dynamically then you could use the code below which is completely different from the things i said above. for that you could use platform.runlater() inside another thread which deals with everything that is about to change in realtime
new Thread(new Runnable()
{
#Override
public void run()
{
Platform.runLater(new Runnable()//use platform.runlater if you are using javafx
{
#Override
public void run()
{
try
{Thread.sleep(50);}catch(Exception e){}//use it in for loop where changes happen
//do some realtime change of components
}
});
}).start();
your Console class would be
import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
public class Console extends JPanel{
private JTextArea area;
public Console(){
this.setBorder(new TitledBorder("Console:"));
area=new JTextArea();
this.setLayout(new BorderLayout());
JScrollPane scroll=new JScrollPane(area);
this.add(scroll,BorderLayout.CENTER);
}
public void clear(){
area.setText("");
}
public void write(String txt){
area.append(txt+" "+"\n");
}
}
and the Calculator class is
public class Calculator {
private Frame parent;
public Calculator(Frame f){
parent=f;
}
public void calculate(){
new Thread(new Runnable() {
#Override
public void run()
{
for (int i = 0; i <= 100; i++) {
try
{
Thread.sleep(50);
}
catch(Exception e)
{
e.printStackTrace();
}
parent.writeOnConsole("Iteration "+i);
}
}
}).start();
}
}
as you can see i used another thread to do the changes
try the update method to call paint method for maintain every change
I am starting learning Java, and I wanted to create a simple camera feed viewer using OpenCV.
MyCV class works just fine when instantiated from its own main() method, or when the call is made from within the main() method of a caller class.
I then built a "MyClient" class, with a main() method and a simple GUI (just a JFrame and a JButton, really), because I want MyCV GUI to be shown when pressing a button in MyClient GUI.
The problem is, when the "caller" class has its own JFrame and associated GUI elements, and I click the button, the whole GUI freezes, the frame from MyCV class shows up empty, and both windows become unresponsive.
I've also tried the class with SwingUtilities.invokeLater() , to no avail.
As I said, I'm a novice at Java/Swing, and it looks to me like a paintComponent() issue, but for the life of me, I can't fix it.
Any help is greatly appreciated
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
public class MyCV {
public static void main(String args[]){
MyCV cv=new MyCV();
}
public MyCV(){
System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
//-- Video capture structure
VideoCapture feed=new VideoCapture(0);
//-- Mat structures
Mat imgFeed1=new Mat();
Mat imgFeed2=new Mat();
Mat imgFeed3=new Mat();
Mat imgFeed4=new Mat();
imgPanel feedPanel1=new imgPanel(); feedPanel1.setPreferredSize(new Dimension(400,400));
imgPanel feedPanel2=new imgPanel(); feedPanel2.setPreferredSize(new Dimension(400,400));
imgPanel feedPanel3=new imgPanel(); feedPanel3.setPreferredSize(new Dimension(400,400));
imgPanel feedPanel4=new imgPanel(); feedPanel4.setPreferredSize(new Dimension(400,400));
JPanel container=new JPanel(); container.setPreferredSize(new Dimension(800,800));
container.add(feedPanel1);
container.add(feedPanel2);
container.add(feedPanel3);
container.add(feedPanel4);
JFrame f=new JFrame("MyChild");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.add(container);
f.setSize(1300, 800);
f.setVisible(true);
//-- clear resources on exit
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
feed.release();
f.dispose();
}
});
//-- main loop
while(true){
if(!feed.isOpened()) break;
feed.read(imgFeed1);
if(imgFeed1.size().width==0) break;
//-- display images in panels (Colors +Feed +Norm)
feedPanel1.setimagewithMat(imgFeed1);
feedPanel2.setimagewithMat(imgFeed1);
feedPanel3.setimagewithMat(imgFeed1);
feedPanel4.setimagewithMat(imgFeed1);
// repaint frame
container.repaint();
}
}
class imgPanel extends JPanel{
private static final long serialVersionUID=1L;
private BufferedImage image;
//public imgPanel(){ super(); }
private BufferedImage getimage(){ return image; }
public void setimage(BufferedImage newimage){image=newimage; return; }
//-- called method
public void setimagewithMat(Mat newimage){
image=matToBufferedImage(newimage);
return;
}
//--
//--
public BufferedImage matToBufferedImage(Mat matrix) {
int cols = matrix.cols();
int rows = matrix.rows();
int elemSize = (int)matrix.elemSize();
byte[] data = new byte[cols * rows * elemSize];
int type;
matrix.get(0, 0, data);
switch (matrix.channels()) {
case 1:
type = BufferedImage.TYPE_BYTE_GRAY;
break;
case 3:
type = BufferedImage.TYPE_3BYTE_BGR;
// bgr to rgb
byte b;
for(int i=0; i<data.length; i=i+3) {
b = data[i];
data[i] = data[i+2];
data[i+2] = b;
}
break;
default:
return null;
}
BufferedImage image2 = new BufferedImage(cols, rows, type);
image2.getRaster().setDataElements(0, 0, cols, rows, data);
return image2;
}
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
BufferedImage temp=getimage();
if(temp!=null){
g.drawImage(temp, 0, 0, temp.getWidth(), temp.getHeight(), this);
}
}
}
}
MyClient.java:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class MyClient {
public static void main(String args[]){
//MyCV cv=new MyCV();
//MyClient cli=new MyClient();
JFrame f=new JFrame("My Client");
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
JButton button=new JButton("call Color Detector");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
MyCV cv=new MyCV();
}
});
f.add(button);
}
public MyClient(){
JFrame f=new JFrame("My Client");
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
JButton button=new JButton("call Color Detector");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
MyCV cv=new MyCV();
}
});
f.add(button);
}
}
while(true){
The above is an infinite loop that runs on the Event Dispatch Thread (aka EDT). Swing is single threaded - all events, painting, etc...occur on a single thread (the EDT) - if that Thread is for any reason tied up doing work none of its other responsibilities can occur (in other words, it looks like your user interface locks up). The main method runs on a separate Thread, so you do not see the same behavior (although all calls to Swing components should be placed on the EDT so I would not necessarily call this the correct way to get around the behavior). Three options:
Start one or more new Threads and place any long running tasks within that thread (note that any calls to Swing Components should be placed on the EDT using SwingUtilities)
Use a SwingWorker
If you wish to do something periodically, such as animation, you can use a java.swing.Timer
An example of 1:
//long running task inside a new Thread
new Thread(new Runnable(){
#Override
public void run(){
while(true){
}
}
});
I wanted to create a JFrame and put a sequence of images for animation in there. But the images don't appear in the frame window. I just want basic troubleshooting tips to make it appear in the window. Just edit the code for an answer if you can.
My question:
Why isn't the window displaying any pictures? It shows a window with a background color of blue, but that's it. Please tell me an efficient way to store images in variables and display it in a loop.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.*;
public class Game extends JLabel implements ActionListener{
/**
*
*/
private static final long serialVersionUID = 1L;
public static Game blah;
BufferedImage nekopics[] = new BufferedImage[7];
BufferedImage currentimg;
public String nekosrcs[];
int xpos;
Timer timer;
public Game() throws IOException
{
JFrame jframe = new JFrame();
nekosrcs = new String[] { "walk1.png", "walk2.png",
"walk3.png", "walk4.png", "walk5.png",
"walk6.png"};
jframe.setTitle("Game");
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.setLayout(new FlowLayout());
jframe.setSize(400, 400);
jframe.setResizable(false);
jframe.setVisible(true);
jframe.getContentPane().setBackground(Color.BLUE);
for (int i=0; i < nekopics.length; i++) {
nekopics[i] = ImageIO.read(new FileInputStream("D:/Programs
/pics"+nekosrcs[i]));
}
for (int i=0; i < nekopics.length; i++) {
timer = new Timer(1000, this);
timer.setInitialDelay(0);
timer.start();
currentimg = nekopics[i];
repaint();
}
}
public void paintComponent(Graphics g)
{
super.paint(g);
g.drawImage(currentimg,100,100,this);
}
public static void main(String[] args) throws IOException {
blah = new Game();
}
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
}
Alright, there are a lot of problems in your code, let's step into each of them:
You have a lot of spaces between lines, that makes your code a lot larger and harder to read
You haven't indented your code correctly (see the last } on your code, it's at the same level than the others; your for loops, etc), it makes the code so much harder to read and understand as well
You're creating a JFrame but extending JLabel, I'm not sure why you're doing this, if you're doing it so you can use the paintComponent() method, it's not necessary, on my code you can see how you can do it w/o extending any Component
If you haven't read the Swing Timer docs you should click that link and read what the ActionListener parameter does. In this case, we're going to use this listener to call the repaint() method and update our currentImage (or nextImage in the code below) and change the image accordingly. You failed to do this.
You were creating more than 1 Timer too, you created 6 here! All of them new but they had no action to do when the time finished
for (int i=0; i < nekopics.length; i++) {
timer = new Timer(1000, this);
timer.setInitialDelay(0);
timer.start();
currentimg = nekopics[i];
repaint();
}
You're changing unnecessarily the visibility of the paintComponent() method to public from protected
However I want to congratulate you for not using a null layout and following the recommendations I made on the comments above.
And finally the code that changes one image for another inside a Timer is the following, you can copy-paste it and change the image's names so you can see how it works.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageSequence {
private JFrame frame;
private JPanel pane;
private Timer timer;
private int nextImage = 0;
private String[] images = {"tokyo", "tokyo2", "starwars"};
private Image img = null;
public static void main (String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ImageSequence().createAndShowGui();
}
});
}
public void createAndShowGui() {
frame = new JFrame("Image Sequence");
timer = new Timer(1000, listener);
pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
try {
img = ImageIO.read(new FileInputStream("/home/jesus/Pictures/" + images[nextImage] + ".jpg"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
g.drawImage(img , 0, 0, 200, 200, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
};
timer.start();
frame.getContentPane().add(pane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
System.out.println(nextImage);
nextImage = nextImage < images.length - 1 ? nextImage + 1 : 0;
System.out.println(nextImage);
pane.repaint();
}
};
}
I have:
A JFrame with a JButton on it.
A separate Canvas subclass to show animations.
And I wish to, at the press of the JButton bring up a new JFrame displaying the Canvas subclass as it animates.
The problem I face right now is that the new JFrame appears, however it doesn't get a chance to render anything and the JButton on the main frame stays depressed. The logic I figure behind this is that the EDT hasn't finished doing it's jobs such as showing the JButton as released and so does not get a chance to run the animation method and ends up in deadlock.
This logic treated me well in the past as I made this work by creating a new thread, but having learned more about Java, threads and Swing lately I've come to know that all Swing related events must be handled on one thread: the EDT.
This confuses me as to how I got it working before but lead me to believe that using invokeLater would help the problem; as the job of making the JFrame visible and showing animation would be placed at the end of the queue allowing the JButton to unrelease etc. I've had no luck however; have I completely misunderstood something?
Thanks!
(Also please no comments on my use of the Canvas class as opposed to JPanel, I have my reasons).
Sample code:
Test5 (class with main method).
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*
public class Test5 {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test5().setup();
}
});
}
private void setup() {
JFrame frame = new JFrame("Test");
JButton button = new JButton("Click here");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
newFrame();
}
});
}
});
frame.getContentPane().add(button);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private void newFrame() {
JFrame newFrame = new JFrame("The new frame");
newFrame.setVisible(true);
newFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
CanvasClass canvas = new CanvasClass();
newFrame.getContentPane().add(canvas);
newFrame.pack();
canvas.runAnimation();
}
}
CanvasClass (Canvas subclass)
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
public class CanvasClass extends Canvas {
int x;
public CanvasClass() {
setSize(new Dimension(550,550));
this.x = (int) (Math.random() * 255);
}
//#Override
public void paint(Graphics g) {
g.setColor(new Color(x, x, x));
g.fillOval(0,0,500,500);
}
void runAnimation() {
while (true) {
randomise();
repaint();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void randomise() {
x = (int) (Math.random() * 255);
}
}
You actualy invoke it in EDT but it's blocked in the canvas.runAnimation();
Place the code to be executed in a separate Thread (where you can call sleep) but call the repaint() in SwingUtilities.invokeLater()
Or even better to define a javax.swing.Timer and call the runAnimation() in the Timer's actionPerformed()
UPDATE:
int delay = 20; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
canvasInstance.randomise();
canvasInstance.repaint();
}
};
new Timer(delay, taskPerformer).start();
to be called instead of the runAnimation()