why does a child window hide when I hide the parent window - java

In the below program when I make a call to hide the parent window and then check the status of child window. I can see that the child window also goes off. Why it that?
Is this a feature that whenever we hide a parent window then all child window also gets hidden.
I know it seems obvious but is it documented somewhere?
The definition of setVisible() only says that :
if true, makes the Dialog visible, otherwise hides the Dialog. If the dialog and/or its owner are not yet displayable, both are made displayable. The dialog will be validated prior to being made visible. -> it only says about the owner dialog but nothing about a child dialog.
Program :
import java.awt.GridLayout;
import java.awt.Window;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
class MyDialogParent extends JDialog {
MyDialogParent ()
{
super(null,ModalityType.MODELESS);
// super.setUndecorated(true);
super.setVisible(false);
// Disable Alt-F4 and any other close key sequences (MMSmk90468).
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
#Override
public void setVisible(boolean b)
{
System.out.println("parent visiblity its overridden method b : "+b);
super.setVisible(b);
}
}
class MyDialogChild extends JDialog {
MyDialogChild (Window parent)
{
super(parent,ModalityType.MODELESS);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
}
#Override
public void setVisible(boolean b)
{
System.out.println("child visibility in its overridden method b : "+b);
super.setVisible(b);
}
}
public class MyDialogs {
public static void main(String args[])
{
MyDialogParent parent=new MyDialogParent();
parent.setSize(400, 400);
System.out.println("parent.isVISIBLE : "+parent.isVisible());
parent.setVisible(true);
System.out.println("parent.isVISIBLE a : "+parent.isVisible());
System.out.println("1");
System.out.println("2");
System.out.println("3");
MyDialogChild child=new MyDialogChild(parent);
child.setLocationRelativeTo(parent);
child.setSize(300, 300);
child.setLayout(new GridLayout());
child.add(new JPanel());
System.out.println("child.isVISIBLE : "+child.isVisible());
child.setVisible(true);
System.out.println("child.isVISIBLE a : "+child.isVisible());
System.out.println("5");
System.out.println("6");
System.out.println("parent.isVISIBLE : "+parent.isVisible());
System.out.println("child.isVISIBLE : "+child.isVisible());
parent.setVisible(false);
System.out.println("parent.isVISIBLE a : "+parent.isVisible());
System.out.println("child.isVISIBLE a : "+child.isVisible());
// child.setVisible(true);
}
}

Related

How to close all children and parent windows?

I need to close all the children and parent windows when I close one window in the hierarchy.
I have three lines of windows:
My windows hierarchy - All lines start in one MainWindow. Only LoadPlayers and CreatePlayers are dialogs. All other windows are frames.
E.g. I close TablesOverview in the first line - I need to close all other windows in this line.
But windows in other lines must stay open.
Notice that TablesOverview is in two lines.
I can write the code, where I named every window that must close. But I need cleaner solution.
This code give me all opened windows - I don't know how to take only windows in one line.
Window[] windows = Window.getWindows();
This codes give me nothing.
Window[] windows = frame.getOwnedWindows();
Component comp = frame.getParent();
Java don't provides this support implicitly. So, you have to use some work around:
Solution:
Create a new class which extends JFrame
Create a data member children of type ArrayList<Your class name>. This holds the instance created by current window
Create an member method createChildWindow(). This method will create a child window and add it in to variable children
Register for event on closing the window. For this use addWindowListener() and override public void windowClosing(java.awt.event.WindowEvent windowEvent) method. In this method traverse throw the children collection and close each child(This will eventually close its subsequent children also).
Following is the complete working code
WindowTracker.java
package com.cse.test.awt;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class WindowTracker extends JFrame
{
ArrayList<WindowTracker> children;
JLabel childrenCount;
public WindowTracker()
{
children = new ArrayList<>();
this.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent arg0) {
System.out.println("Closing..." + this.hashCode());
for(WindowTracker child:children)
{
child.dispatchEvent(new WindowEvent(child, WindowEvent.WINDOW_CLOSING));
}
System.out.println("Closed..." + this.hashCode());
}
});
this.setLayout(new FlowLayout(FlowLayout.RIGHT));
childrenCount = new JLabel("Children count: 0");
this.add(childrenCount);
JButton btn = new JButton("Create new child");
btn.addActionListener(e -> {WindowTracker child = getNewChild();});
this.add(btn);
this.setSize(300, 300);
this.setTitle("" + this.hashCode());
this.setVisible(true);
}
public WindowTracker getNewChild()
{
WindowTracker child = new WindowTracker();
children.add(child);
childrenCount.setText("Children count: " + children.size());
return child;
}
}
Executer.java
package com.cse.test.common;
import com.cse.test.awt.WindowTracker;
public class Executer {
public static void main(String[] args) {
// TODO Auto-generated method stub
new WindowTracker();
}
}

JDialog not Displaying until 'dispose' Executed

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.

Different behavior of JDialog when is created by program and by user action

I have 3 clasess : Loader, MyDialog and TEST(with main method). (for code see below)
Everything I want to achieve is create simple dialog with JLabel and JProgressBar, which will notify user about how much time remains to show MyDialog. MyDialog is Jdialog with time consuming operation in constructor (loading data from database etc.).
In code below is model situation. When "MyDialog" is created by main (constant BY_USER is false), everything working exactly i want to. But when i make dialog with button , and instance of MyDialog is created after button press (constant BY_USER is true), Loader is blank white form. It looks like is not completed.
Loader is extending Thread, so i suppose that problem will be in threading (event dispatch thread)? I dont know, what is wrong and how fix it. Please help.
Thanks and sorry for my English.
CLASS TEST :
package test;
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;
import javax.swing.WindowConstants;
public class TEST {
public static final boolean BY_USER = false;
public static void main(String[] args) {
if (BY_USER) {
JFrame mainDialog = new JFrame("Main");
JButton show = new JButton("Show MyDialog");
show.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MyDialog dialog = new MyDialog();
}
});
mainDialog.add(show);
mainDialog.setLocationRelativeTo(null);
mainDialog.setMinimumSize(new Dimension(160, 80));
mainDialog.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainDialog.setVisible(true);
} else {
MyDialog dialog = new MyDialog();
}
}
}
CLASS MyDialog :
package test;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class MyDialog extends JFrame{
public MyDialog() {
super();
// making loader with title, first message and count of steps of operation
Loader loader = new Loader("Loader", "First showed message", 100);
loader.ShowLoader();
// time-consuming operation (loading data from database etc.).
// for clarity replaced with for statement
int j=0;
for(int i=0; i<Integer.MAX_VALUE; i++)
{
j++;
if(j==Integer.MAX_VALUE/100){
// updating loader message and progress bar value
loader.NewAction(Integer.MAX_VALUE - i+"");
j=0;
}
}
// closing loader
loader.DestroyLoader();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setSize(300, 300);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
CLASS Loader:
package test;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
public class Loader extends Thread{
private JDialog dialog;
private JLabel message = new JLabel("", SwingConstants.CENTER);
private JProgressBar progressBar = new JProgressBar(0, 100);
private String newMessage;
private double percentForStep;
private int remainingSteps;
public Loader(String taskName, String firstMessage, int steps) {
this.remainingSteps = steps-1;
dialog = new JDialog((Dialog) null, taskName);
dialog.setLayout(new BorderLayout(15, 15));
dialog.add(message, BorderLayout.CENTER);
dialog.add(progressBar, BorderLayout.SOUTH);
message.setText(firstMessage);
percentForStep = 100 / steps;
}
public void ShowLoader()
{
dialog.setMinimumSize(new Dimension(400,120));
dialog.setLocationRelativeTo(null);
dialog.setVisible(true);
this.start();
}
public void DestroyLoader(){
dialog.dispose();
this.interrupt();
}
public void NewAction(String newMessage){
this.newMessage = newMessage;
this.remainingSteps--;
Lock.changed = true;
}
public int RemainingStepsCount()
{
return remainingSteps;
}
#Override
#SuppressWarnings({"CallToThreadYield", "SleepWhileInLoop"})
public void run() {
do{
synchronized (Lock.class) {
if (Lock.changed) {
Lock.changed = false;
this.message.setText(newMessage);
this.progressBar.setValue((int)(100-(remainingSteps*percentForStep)));
dialog.repaint();
}
dialog.repaint();
}
}while(true);
}
}
class Lock{
static boolean changed = false;
}
Look to SwingWorker and his use; I think it can help you to solve the problem.

Cancel a Java Webstart custom download

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.

Why is paint()/paintComponent() never called?

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.

Categories