I am trying to create a JDialog that will remain open until it is closed and not permit the user to close it.
import java.awt.Dialog.ModalityType;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class MessageClass {
private JDialog waitDialog;
public MessageClass(String string_,String title_) {
JOptionPane msg=new JOptionPane(string_,
JOptionPane.INFORMATION_MESSAGE,
JOptionPane.DEFAULT_OPTION,null,new Object[]{},null);
waitDialog=msg.createDialog(title_);
}
public void openWait() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
waitMessage();
}
});
}
private void waitMessage() {
waitDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
waitDialog.setLocationRelativeTo(GUI.getInstance().frame);
waitDialog.setModalityType(ModalityType.DOCUMENT_MODAL);
waitDialog.pack();
waitDialog.setVisible(true);
}
public void closeWait() {
waitDialog.dispose();
}
}
I have 3 problems with this class that I cannot understand. The first is that the pane will not display until the dispose is called. The second is that it will not dispose which is probably related to the first problem (I haven't tried calling dispose twice). The third is that the pane does not display the text but rather is blank.
I set up the class like this:
MessageClass msg=new MessageClass("Information text","Title");
msg.openWait();
// a bunch of code
msg.closeWait();
Can someone spot what I am doing wrong? TIA.
Where are you running openWait() and closeWait() ? If they are on the EventDispatchThread, you block that thread until the closeWait() finishes execution, so the dialog does not get a chance to draw itself.
Related
I use javafx, I have a TextField and a Button, when the button is pressed, it saves what is written in the TextField in a String. What I want to create is a method to mark a pause, while waiting for the Button to get pressed.
I have a class named pause.java, where I tried to put a obj.wait(); and a notifyAll(); in the event where the button is pressed, but the window isn't accessible during this time, I can't press the button or enter anything in the TextField.
So what I found was to put the obj.wait(); in a task, then I don't know why but it directly breaks out of the wait.
Here is my pause.java
package net.jpajavafx;
import java.util.logging.*;
import javafx.concurrent.Task;
public class pause {
Logger logger = Logger.getLogger(pause.class.getName());
MainController obj = new MainController();
public void waitinput() {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
synchronized (obj) {
try {
String write = "Waiting for input...";
logger.log(Level.INFO, write);
obj.wait();
logger.log(Level.INFO, "Done");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
};
new Thread(sleeper).start();
}
}
How do I have to modify it to make it wait, while still having access to the GUI?
Here's my code simplified for the problem:
AlbumManager.java, where my main is.
package net.jpajavafx;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.*;
import javafx.application.*;
import javafx.fxml.FXMLLoader;
public class AlbumManager extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Album Manager");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java:
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
notifyAll();
}
public void startprogram() {
int etat = 0;
int run = 1;
while (run == 1) {
textarea.setText("1: launch method");
pause.waitinput(); // here I want to wait for an input
etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
}
It's really not clear what you're trying to achieve here that needs a separate thread: all the separate thread seems to try to do is wait until the button is pressed, and then execute some code. That functionality is already provided by the event management system in JavaFX (and the same is true for any UI toolkit): just execute the code in the event handler.
(As an aside, your use of wait() is incorrect, and if you fix that, the thread will never wake up because you are not calling notifyAll() on the same object on which you are calling wait().)
You can achieve what you seem to be trying to do simply with
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
processInput();
}
public void processInput() {
int etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
You have to start another thread using a Runnable, so your UI thread does not get blocked while the save-operation completes.
You can do this by placing a listener on the button that will start the save-operation on a new thread when the button is clicked.
The code for adding a listener to a button that starts a new thread would look something like this:
//Creating the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
MainController controller = new MainController();
controller.start();
}
};
//Registering the event filter
button.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
The code you posted doesn't really do anything. Your call to waitinput() only logs and calls wait(). wait() is not what you want, since this operation is intended for putting a thread on hold until it is notified, not for executing a task in a seperate thread. Remove the obj.wait(), and add a listener that calls your logging method when the button is clicked. Also, get rid of the while-loop. The EventHandler will take care of events in the background.
I have recently started learning java.I want to make a game like https://sites.google.com/site/millseagles/home/Games/multiplayer/tron
I have made it in c++ once using a simple graphics lib. I have the graphics part down i plan to use small images and use http://horstmann.com/sjsu/graphics/ this basic graphics lib.I can't figure out keyboard input i want it so if you press an arrow the picture adds a small green square(I have a green.png).I can't figure out to use keyboard listeners.I get all these errors.I just need a simple lib that i can say getKey() or something and i can use if() to figure out the action.this is the code I have.I was messing with the key event but don't understand it.
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.*;
public class game implements KeyListener
{
public void keyReleased(KeyEvent e){}
public void keyTyped(KeyEvent e){}
public game()//snake like game
{
}
public void test()
{
int x=30,y=30;//middle total 60x60
tile[] map=new tile[3600];//tile is a class i made that is a picture and some int and bool using the simple lib i linked 60 by 60 tiles
for(int i=0;i<3600;i++)
{
map[i]=new tile();
}
}
public void keyPressed(KeyEvent e)//this does not work i want it to work when a key is clicked
{
while(x>0)//this part works when it is not in the keypressed function
{
map[(y*60)+x].load(4);//4 refrences a green rectangle image
map[(y*60)+x].draw(x,y,10);//draw it based on x and y 10 pixels sized tiles
x--;//make a line going left
}
}
}
I know this may be messy.I have tested my code it works it just breaks when i try to implement keyboard events.If you can point me to a much more beginner friendly lib that would be great.
You simply have to add the listener to something (e.g. the window where the game is being played).
I will give you an example, where we will simply display the code of the key being stroked.
This is the class where you produce the interface:
import java.awt.Dimension;
import javax.swing.JFrame;
public class Game {
public static void main(String[] args) {
/* Creating a window (300x400) */
JFrame frame = new JFrame("Add your own title");
frame.setPreferredSize(new Dimension(300, 400));
/* This is the part where we add the keyListener (notice that I am also sending
* this window as a parameter so that the listener can modify it)*/
frame.addKeyListener(new ArrowListener(frame));
/* Making the window visible */
frame.pack();
frame.setVisible(true);
}
}
And this is the class where we have the listener:
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class ArrowListener implements KeyListener {
/* We keep the window as an instance variable so we can modify it once the event is triggered */
JFrame frame;
/* This is the constructor */
public ArrowListener(JFrame j) {
frame = j;
}
/* This is where the magic happens */
public void keyPressed(KeyEvent e) {
/* Modify this with what you actually want it to do */
/* We clear the panel so we can add new text without any other text behind it */
frame.getContentPane().removeAll();
/* We add some text that actually shows the keyCode (left arrow = 37, top = 38, right = 39, bottom = 40) */
frame.add(new JLabel("Key Code #" + String.valueOf(e.getKeyCode())));
/* Redrawing the window */
frame.revalidate();
}
/* These two are part of the contract we made when we decided to
* implement the KeyListener */
public void keyTyped(KeyEvent e) { /* Do nothing */ }
public void keyReleased(KeyEvent e) { /* Do nothing */ }
}
Note: when running the program press the keys to see the text appearing on the window.
I didn't get around the library you were using, but I used the most popular one called swing (tutorial)
In the following code, I call JOptionPane.showMessageDialog, inside a try/catch block. But when the error is caught, my JOptionPane is visible but without any message !!! Does someone knows why and how I can correct the problem ?
Regards
MyBoardJPannel.java
package experimentations.gui;
import java.awt.Graphics;
import java.awt.Image;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MyBoardPannel extends JPanel {
#Override
public void paint(Graphics grahics) {
if (imageToShow == null)
imageToShow = loadImage("sampleImage");
}
/**
* In fact, there are not any image in project => will go to catch clause.
* #param imageName
*/
private void loadImage(String imageName) {
InputStream imageStream = getClass().getResourceAsStream("/"+imageName+".png");
try {
imageToShow = ImageIO.read(imageStream);
}
catch (Exception e) {
String errorMessage = "Failed to load image "+imageName;
System.err.println(errorMessage);
JOptionPane.showMessageDialog(this, errorMessage,
"Image loading error", JOptionPane.ERROR_MESSAGE);
imageToShow = null;
System.exit(1);
}
}
private Image imageToShow;
}
JOptionPaneErrorShowing.java
package experimentations.gui;
import javax.swing.JFrame;
public class JOptionPaneErrorShowing extends JFrame {
public JOptionPaneErrorShowing(){
setTitle("JOptionPane experimentation");
setSize(300, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
add(new MyBoardPannel());
}
/**
* #param args
*/
public static void main(String[] args) {
new JOptionPaneErrorShowing().setVisible(true);
}
}
It's likely a Swing concurrency issue. But more importantly, you should never load an image from within a paint or paintComponent method, ever. Read it in the constructor or elsewhere but paint/paintComponent need to be lean and blazingly fast.
To solve your issue, consider reading in the image in SwingWorker object. If you call a JOptionPane from within the SwingWorker's doInBackground method though, be sure to call it on the Swing event thread, the EDT, using SwingUtilities.invokeLater(Runnable).
Also, you will hardly ever want to draw in a JPanel's paint method unless you are taking care of painting borders and children. Instead paint in a paintComponent method, and don't forget to call the super.paintComponent(g) method in that paintComponent override. You'll want to read the Swing graphics tutorials as this is all spelled out there.
For example:
import java.awt.Graphics;
import java.awt.Image;
import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MyBoardPannel extends JPanel {
protected static final String SAMPLE_IMAGE = "sampleImage";
Image imageToShow = null;
public MyBoardPannel() {
SwingWorker<Image, Void> mySW = new SwingWorker<Image, Void>() {
#Override
protected Image doInBackground() throws Exception {
return loadImage(SAMPLE_IMAGE);
}
#Override
protected void done() {
try {
imageToShow = get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
mySW.execute();
}
#Override
public void paintComponent(Graphics grahics) {
super.paintComponent(grahics);
if (imageToShow != null) {
grahics.drawImage(imageToShow, 0, 0, null);
}
}
private Image loadImage(String imageName) {
InputStream imageStream = getClass().getResourceAsStream(
"/" + imageName + ".png");
try {
return ImageIO.read(imageStream);
} catch (Exception e) {
final String errorMessage = "Failed to load image " + imageName;
System.err.println(errorMessage);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(MyBoardPannel.this, errorMessage,
"Image loading error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
});
}
return null;
}
}
I don't really know, but maybe your panel you use as parent of the JOptionPane (by passing this) is invisible or there is something else wrong. Try adding pack(); at the end of your JOptionPaneErrorShowing constructor.
What I know is that I had this problem when I was using an old Ubuntu and old Nvidia driver for my GPU, when the desktop effects were turned on (the Compiz Fusion of today. I don't know if it was already called Compiz, that long ago).
Aha! I found it, you are displaying the error inside the repaint method. Never do that! Load your image inside the constructor of the MyBoardPanel class and show error messages over there.
when you download a resource in a Java Webstart application there's usually a download progress window displayed which shows the progress of the download. If this window is the default progress window, it has a cancel button. I'm basically trying to implement this cancel button in a custom download progress window.
As there is no method which you could call to cancel the download, I tried to find out how this was done in the default progress window. Because of the implementation with a ServiceManager it's a bit tricky to find the actual implementation. But I finally found this: [jdk-source on googlecode (DownloadServiceImpl)].
When you search for "cancel" or just scroll down to the progress method you will see that it should be as easy as throwing a RuntimeException. Sadly this doesn't really work. It just stops the progress method from being called. The resource is still downloaded in the background and the loadPart method never returns.
If you want to try this for yourself, I've prepared a small example. You will need some sort of webserver though (a local webserver is sufficient of course). I have tried this on a Windows XP (32 bit) with Java 1.6.0_21 (and apache tomcat 6).
A simple jnlp file would look like this (you probably want to change the port):
<?xml version="1.0" encoding="utf-8"?>
<jnlp
spec="1.0+"
codebase="http://127.0.0.1:8080/DownloadTest"
href="DownloadTest.jnlp"
version="1.0">
<information>
<title>DownloadTest</title>
<vendor>Download Tester</vendor>
</information>
<resources os="Windows">
<java version="1.6.0_18+" href="http://java.sun.com/products/autodl/j2se" />
<jar href="DownloadTest.jar" main="true"/>
<jar href="largeResource.jar" download="lazy" part="One"/>
</resources>
<application-desc main-class="downloadtest.Main">
</application-desc>
</jnlp>
Next you will need a large file as resource (the content doesn't matter at all). For example on many windows machines you you will find "driver.cab" under "Windows\Driver Cache\i386". The file must be added to a jar archive (jar -cf largeResource.jar <input file>).
The main program looks like this (you will need to include jnlp.jar as lib, which you can find at <jdk_home>\sample\jnlp\servlet):
package downloadtest;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.jnlp.DownloadService;
import javax.jnlp.DownloadServiceListener;
import javax.jnlp.ServiceManager;
import javax.jnlp.UnavailableServiceException;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
public class Main {
private static DownloadService downloadService;
private static DownloadServiceListener customDownloadWindow;
static {
try {
downloadService = (DownloadService) ServiceManager.lookup("javax.jnlp.DownloadService");
} catch (UnavailableServiceException ex) {
System.err.println("DownloadService not available.");
}
customDownloadWindow = new CustomProgress();
}
public static void main(String[] args) {
JFrame frame = new JFrame("DownloadTest");
frame.setBounds(0, 0, 200, 100);
frame.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
frame.setLayout(null);
JButton startDownload = new JButton("download");
startDownload.setBounds(20, 20, 150, 40);
startDownload.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() {
try {
downloadService.loadPart("One", customDownloadWindow);
//downloadService.loadPart("One", downloadService.getDefaultProgressWindow());
} catch (IOException ex) {
ex.printStackTrace();
System.err.println("IOException loadPart.");
}
return null;
}
}.execute();
}
});
frame.add(startDownload);
frame.setVisible(true);
}
}
You can try each download progress window by uncommenting one "downloadService.loadPart..." line and commenting out the other one.
And finally the custom progress window itself:
package downloadtest;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.jnlp.DownloadServiceListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
public class CustomProgress implements DownloadServiceListener {
JFrame frame = null;
JProgressBar progressBar = null;
boolean uiCreated = false;
boolean canceled = false;
public CustomProgress() {
}
private void create() {
JPanel top = createComponents();
frame = new JFrame(); // top level custom progress indicator UI
frame.getContentPane().add(top, BorderLayout.CENTER);
frame.setBounds(300,300,400,300);
frame.pack();
updateProgressUI(0);
}
private JPanel createComponents() {
JPanel top = new JPanel();
top.setBackground(Color.WHITE);
top.setLayout(new BorderLayout(20, 20));
String lblText = "<html><font color=green size=+2>JDK Documentation</font>" +
"<br/> The one-stop shop for Java enlightenment! <br/></html>";
JLabel lbl = new JLabel(lblText);
top.add(lbl, BorderLayout.NORTH);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setStringPainted(true);
top.add(progressBar, BorderLayout.CENTER);
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
CustomProgress.this.canceled = true;
}
});
top.add(cancelButton, BorderLayout.SOUTH);
return top;
}
public void progress(URL url, String version, long readSoFar,
long total, int overallPercent) {
updateProgressUI(overallPercent);
}
public void upgradingArchive(java.net.URL url,
java.lang.String version,
int patchPercent,
int overallPercent) {
updateProgressUI(overallPercent);
}
public void validating(java.net.URL url,
java.lang.String version,
long entry,
long total,
int overallPercent) {
updateProgressUI(overallPercent);
}
public void downloadFailed(URL url, String string) {
System.err.println("Download failed");
}
private void updateProgressUI(int overallPercent) {
if (overallPercent > 0 && overallPercent < 99) {
if (!uiCreated) {
uiCreated = true;
// create custom progress indicator's UI only if
// there is more work to do, meaning overallPercent > 0 and < 100
// this prevents flashing when RIA is loaded from cache
create();
}
progressBar.setValue(overallPercent);
if (canceled) {
throw new RuntimeException("canceled by user");
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setVisible(true);
}
});
} else {
// hide frame when overallPercent is above 99
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (frame != null) {
frame.setVisible(false);
frame.dispose();
}
}
});
}
}
}
This is basically taken from an Oracle tutorial (http://download.oracle.com/javase/tutorial/deployment/webstart/customProgressIndicatorForAppln.html). I just added a cancel button.
When you build this as a jar file and put it together with the largeResource.jar and DownloadTest.jnlp in a public folder of your webserver, you should be able to start the application via your web browser. Then click the download button and before it is finished click the cancel button in the download window. After trying the custom progress window you will need to remove the application (or just the resource) from your Java cache (because the resource is downloaded in the background regardless of clicking the cancel button).
So, why is this working with the default progress window but not with the custom progress window? Is there an easy possibility to cancel a download with a custom download window?
Any help or hints appreciated.
Drax
Ok, took a look at the Google sample you showed and found this at the bottom of the class
/*
* Progress Helper class
*
* The DownloadServiceListerner interface defined in the JNLP API is
* a subset of the DownloadProgressWindow interface used by elsewhere.
*
* this class is used to create a Helper object that implements both.
*/
private class ProgressHelper extends CustomProgress {
private DownloadServiceListener _dsp = null;
public ProgressHelper() {
_dsp = null;
}
public ProgressHelper(DownloadServiceListener dsp) {
setAppThreadGroup(Thread.currentThread().getThreadGroup());
setListener(dsp);
_dsp = dsp;
if (_dsp instanceof DefaultProgressHelper) {
((DefaultProgressHelper) _dsp).initialize();
}
// for bug #4432604:
_dsp.progress(null, null, 0, 0, -1);
}
public void done() {
if (_dsp instanceof DefaultProgressHelper) {
((DefaultProgressHelper) _dsp).done();
} else {
// make sure callbacks to DownloadServiceListener have
// been called before returning (for TCK test)
flush();
}
}
}
And what is interesting is that it looks like it sets the current thread's ThreadGroup as the application thread group. So this leads me to believe that by doing this the actual download is kept closer to the application (not sure what the correct terminology would be) such that the RuntimeException throw in the class in the cancel check really does affect it.
Otherwise, my hunch is that in your application the download actually takes place in another thread and is "unaffected" by the Exception thrown by your application, hence, allowing it to complete.
For the last two days I have tried to understand how Java handles graphics, but have failed miserably at just that. My main problem is understanding exactly how and when paint() (or the newer paintComponent() ) is/should be called.
In the following code I made to see when things are created, the paintComponent() is never called, unless I manually add a call to it myself or calls to JFrame.paintAll()/JFrame.paintComponents().
I renamed the paint() method to paintComponent() in hoping that would fix my problem of it never being called (even at repaint()), but no luck.
package jpanelpaint;
import java.awt.*;
import javax.imageio.*;
import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
public class ImageLoadTest extends JComponent {
ArrayList<Image> list;
public ImageLoadTest() {
list = new ArrayList<Image>();
try { //create the images (a deck of 4 cards)
for(String name : createImageFileNames(4)){
System.err.println(name);
list.add(ImageIO.read(new File(name)));
}
} catch (IOException e) { }
}
protected void paintComponent(Graphics g) {
int yOffset=0;
System.err.println("ImageLoadTest.paintComponent()");
for(Image img : list) {
g.drawImage(img, 0, yOffset, null);
yOffset+=20;
}
}
public static void main(String args[]) throws InterruptedException {
JFrame frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Thread.sleep(1000);
frame.setTitle("Loading images");
ImageLoadTest ilt = new ImageLoadTest();
frame.add(ilt);
//update the screen
//DOESN'T WORK. only works if I call frame.paintAll(frame.getGraphics())
ilt.repaint();
frame.repaint();
Thread.sleep(1000);
frame.setTitle("Setting background");
ilt.setBackground(Color.BLACK);
//update the screen - DOESN'T WORK even if I call paintAll ..
ilt.repaint();
frame.repaint();
//have to call one of these to get anything to display
// ilt.paintComponent(frame.getGraphics()); //works
frame.paintComponents(frame.getGraphics()); //works
}
//PRIVATE HELPER FUNCTIONS
private String[] createImageFileNames(int count){
String[] fileNames = new String[count];
for(int i=0; i < count; i++)
fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp";
return fileNames;
}
}
One of the reasons the paintComponent() doesn't get invoked in the original code is because the component has a "zero size" and the RepaintManger is smart enough not to try and paint something with no size.
The reason the reordering of the code works is because when you add the component to the frame and then make the frame visible the layout manager is invoked to layout the component. By default a frame uses a BorderLayout and by default a component is added to the center of the BorderLayout which happens give all the space available to the component so it gets painted.
However, you change the layout manager of the content pane to be a FlowLayout, you would still have a problem because a FlowLayout respects the preferred size of the component which is zero.
So what you really need to do is assign a preferred size to you your component so layout managers can do their job.
One major issue here is you are not updating your swing components on the Event Dispatch Thread (EDT). Try wrapping all the code in your main method in the following:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// swing code here...
}
});
Also: add your ImageLoadTest to the frame before setting the frame visible. This is based on a quick cursory read of the code -- I will read it further and see what else I can find.
EDIT:
Follow my original advice above, and simplify your main method to look like the following and your paintComponent() will be called:
public static void main(String args[]) throws InterruptedException {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintComponentTest ilt = new PaintComponentTest();
frame.add(ilt);
frame.setVisible(true);
ilt.setBackground(Color.BLACK);
}
});
}
Also I would read up on using timers to perform animation, as well as general Swing event dispatching and how/when to override various paint methods.
http://java.sun.com/products/jfc/tsc/articles/painting/
http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html
http://java.sun.com/docs/books/tutorial/uiswing/concurrency/dispatch.html
To make Tom Hawtin - tackline happy. I rewrote once again
There are several things I changed (check the lines with the //new comment)
Rewrote it completely
Split into a clean new component file (ImageLoadTest.java) and a file to test it (Tester.java)
Improvements on original posters code
call constructor of parent in ImageLoadTest constructor (super())
provided second constructor to set list of images which component should display
IMPORTANT: call to setPreferredSize() of component in constructor. If size isn't set swing of course won't paint your component. preferred size is based on max. width of all images and on sum of all image heights
call to super.paintComponent(g) in overriden paintComponent()
changed paintComponent to automatically base yOffset on height of images being drawn
GUI initialization done on EDT
as original code based on using sleep() to illustrate loading and loading of images could take a long time SwingWorker's are used
worker waits then sets new title and then loads images
on completion the worker in done() finally adds the component to the JFrame and displays it. Added component to content pane of JFrame as described in JFrame api. And as described in javadoc made necessary call to validate() on JFrame after calling add(), as the JFrame is an already visible container whichs children changed.
javdoc citation from validate()
The validate method is used to cause a
container to lay out its subcomponents
again. It should be invoked when this
container's subcomponents are modified
(added to or removed from the
container, or layout-related
information changed) after the
container has been displayed.
second worker just does some more waiting then sets background color to black
used JPanel as baseclass for ImageLoadTest to fix setBackground() which I couldn't get to work with JComponent.
So your main problems where that you didn't set the preferred size of the component and that you did not call validate() on the JFrame after adding something to the already visible container.
This should work
jpanelpaint/ImageLoadTest.java
package jpanelpaint;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
import java.util.List;
public class ImageLoadTest extends JPanel {
private List<Image> list;
public ImageLoadTest() {
super();
}
public ImageLoadTest(List<Image> list) {
this();
this.list = list;
int height = 0;
int width = 0;
for (Image img : list) {
height += img.getHeight(this);
width = img.getWidth(this) > width ? img.getWidth(this) : width;
setPreferredSize(new Dimension(width, height));
}
}
#Override
protected void paintComponent(Graphics g) {
int yOffset=0;
super.paintComponent(g);
System.err.println("ImageLoadTest.paintComponent()");
for(Image img : list) {
g.drawImage(img, 0, yOffset, null);
yOffset+=img.getHeight(this);
}
}
}
Tester.java
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.SwingUtilities;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import jpanelpaint.ImageLoadTest;
public class Tester {
private JFrame frame;
private ImageLoadTest ilt;
private final int NUMBEROFFILES = 4;
private List<Image> list;
//will load the images
SwingWorker worker = new SwingWorker<List<Image>, Void>() {
#Override
public List<Image> doInBackground() throws InterruptedException {
//sleep at start so user is able to see empty jframe
Thread.sleep(1000);
//let Event-Dispatch-Thread (EDT) handle this
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setTitle("Loading images");
}
});
//sleep again so user is able to see loading has started
Thread.sleep(1000);
//loads the images and returns list<image>
return loadImages();
}
#Override
public void done() {
//this is run on the EDT anyway
try {
//get result from doInBackground
list = get();
frame.setTitle("Done loading images");
ilt = new ImageLoadTest(list);
frame.getContentPane().add(ilt);
frame.getContentPane().validate();
//start second worker of background stuff
worker2.execute();
} catch (InterruptedException ignore) {}
catch (ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
};
//just delay a little then set background
SwingWorker worker2 = new SwingWorker<Object, Void>() {
#Override
public List<Image> doInBackground() throws InterruptedException {
Thread.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setTitle("Setting background");
}
});
Thread.sleep(1000);
return null;
}
#Override
public void done() {
ilt.setBackground(Color.BLACK);
frame.setTitle("Done!");
}
};
public static void main(String args[]) {
new Tester();
}
public Tester() {
//setupGUI
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
//start the swingworker which loads the images
worker.execute();
}
//create image names
private String[] createImageFileNames(int count){
String[] fileNames = new String[count];
for(int i=0; i < count; i++)
fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp";
return fileNames;
}
//load images
private List<Image> loadImages() {
List<Image> tmpA = new ArrayList<Image>();
try {
for(String name : createImageFileNames(NUMBEROFFILES)){
System.err.println(name);
tmpA.add(ImageIO.read(new File(name)));
}
} catch (IOException e) { }
return tmpA;
}
}
These were the main problems with the original code that caused it not to work:
not calling validate() after an add() operation
not setting the preferred size of the component.
not calling super.paintComponent() when overriding it (this made the
setBackground() call not work)
I needed to inherit from JPanel in order for it to get painted. Neither Component nor JComponent was sufficient for the setBackground() call to work, even when fixing point 3.
Having done the above, it really didn't matter if calling the method paintComponent or paint, both seemed to work as long as I remembered to call the super constructor at the start.
This info was assembled from what #jitter, #tackline, and #camickr wrote, so big kudos!
P.S. No idea if answering your own question is considered bad form, but since the information I needed was assembled from several answers, I thought the best way was upmodding the other answers and writing a sum up like this.
I recommend reading the first couple of chapters of "Filthy Rich Clients". I had been using Swing for years, but only after reading this book did I finally fully understand exactly how Java's painting mechanism works.