repaint not being called - java

Hi I have googled and can't figured out why my paintComp method isnt being called
I have the following code
package com.vf.zepto.view;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import javax.imageio.ImageIO;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import com.vf.zepto.view.interfaces.ProcessorPanel;
public class CountryDetailsPanel extends JPanel implements ProcessorPanel, Runnable {
private GridBagConstraints c = new GridBagConstraints();
private String countryName;
private Properties prop = new Properties();
private BufferedImage image;
public CountryDetailsPanel() {
try {
prop.load(new FileInputStream("country.props"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//this.setLayout(new GridBagLayout());
c.gridx = 0;
c.gridy = 0;
c.fill = GridBagConstraints.BOTH;
c.insets = new Insets(5, 5, 5, 5);
this.setPreferredSize(new Dimension(200, 200));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
try {
if(countryName != null) {
String asset = prop.getProperty(countryName+".flag");
if(!asset.equals(null)) {
image = ImageIO.read(new File(asset));
g.drawImage(image, 0, 0, null);
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void updateDetails(Object o) {
countryName = (String)o;
SwingUtilities.invokeLater(this);
}
#Override
public void run() {
this.repaint();
}
}
and when calling this.repaint() expect the paintComponent method to be called but for love nor money it isnt.
Have tried to force it to use the EDT incase that was the issue but its not.
any ideas?

You should never call paintComponent() the repaint() method consolidates all requests to change the component (there may be several repaint requests between screen refreshes). It adds an update request to the GUI event queue so that the update will be properly coordinated with other GUI actions (Swing and AWT are not thread-safe). This update request, when processed, calls update(), which calls paint(), which calls your paintComponent()
Why have this:
#Override
public void run() {
this.repaint();
}
It does not seem of any use (creating a new thread to repaint once? Not to mention the thread is not on EDT rather call repaint() on the JPanel instance (if modified externally other than that you shouldnt even worry). However to start a thread which modifeis UI componets use s SwingTimer/SwingWorker or SwingUtilities#invokeXXX()
This might not be related but in your code I see this:
if(!asset.equals(null)) {
image = ImageIO.read(new File(asset));
g.drawImage(image, 0, 0, null);
}
dont use equals() to compare to a null value as this might throw a NullPointerException because you are attempting to
deference a null pointer, for example this code throws a NPE:
String s=null;
if(!s.equals(null)) {//throws NPE
System.out.println("Here");//is never printed
}
Also as mKorbel has said (+1 to him) dont do long running tasks in paintComponent() declare your Image globally, assign it in the constructor and then use it in paintComponent(). F.i
rather do:
public class TestPanel extends JPanel {
private Image image;
public TestPanel() {
image = ImageIO.read(new File(asset));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(asset!=null) {
g.drawImage(image, 0, 0, null);
}
}
}

Do not load image or another hard or long running code in paintComponent
Load this Object as a local variable and only one time
paintComponent() is called
implicitly, when JComponent requires repaint, or
explicitly, for example on every of mouse event if one were to invoke paintComponent() from a MouseMotionListener.
If there is animation, then to use Swing Timer and call repaint().

Related

How to overwrite a BufferedImage drawn to JPanel outside PaintComponent() with getGraphics()

I am working on a simple 2D game. Each tick, I want to check an effects queue that will start a thread for a certain effect(fading transitions, audio fade in and out, etc). For example, pressing "Play" on the menu screen will add a "FadeOut" message to this queue, which will be processed and start a thread to draw a black rectangle with an increasing alpha value over my GamePanel.
I'm overriding paintComponent() and sending my Graphics object to my GameStateManager, which passes along the Graphics object to the current states' draw(). I currently don't have an effects state (which maybe I should) to route the paintComponent() graphics object to, but I do pass my gamepanel to my effects thread, where I can use getGraphics() to draw on it. Drawing a rectangle to the GamePanel directly just causes flickering, as the gameloop is still rendering the game.
I found I can draw a black rectangle with increasing alpha to a BufferedImage, set the composite to AlphaComposite.Src (which causes the new draw to replace the old) then draw the BufferedImage over the game panel. The problem is the BufferedImages drawn to the game panel don't get overridden each draw, so the fade out happens really quickly because these black BufferedImages of various alphas just stack on each other.
I wrote this short program to test composite settings and see what is getting overridden. All drawing is done in the draw(), which would be my run() in the effects thread.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScratchPad extends JPanel implements Runnable
{
private JFrame oFrame = null;
private Thread oGameThread = null;
private Graphics2D oPanelGraphics = null;
private Graphics2D oImageGraphics = null;
private BufferedImage oImage = null;
public static void main(String args[]) throws Exception
{
new ScratchPad();
}
public ScratchPad()
{
createFrame();
initPanel();
addAndShowComponents();
oGameThread = new Thread(this, "Game_Loop");
oGameThread.start();
}
private void addAndShowComponents()
{
oFrame.add(this);
oFrame.setVisible(true);
}
private void initPanel()
{
this.setOpaque(true);
this.setBackground(Color.cyan);
}
private void createFrame()
{
oFrame = new JFrame("Fade");
oFrame.setSize(700, 300);
oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oFrame.setLocationRelativeTo(null);
}
public void run()
{
oImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
while(true)
{
try
{
draw();
Thread.sleep(100);
}
catch(InterruptedException e)
{
}
}
}
private void draw()
{
oPanelGraphics = (Graphics2D)this.getGraphics();
oImageGraphics = oImage.createGraphics();
oImageGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,90));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.setColor(new Color(0,0,0,60));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 220, 10, null);
oImageGraphics.setColor(new Color(0,0,0,30));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 430, 10, null);
// Drawing this image over location of first image, should overwrite first
// after setting composite to 'Src'
oPanelGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,10));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.dispose();
oPanelGraphics.dispose();
}
} // end class
What's interesting is setting the composite on 'oPanelGraphics' causes any alpha to the BufferedImage to go away, resulting in a fully opaque black image being drawn over the image that was previously there. Even setting the color to something other than black doesn't have an effect.
What's also interesting is setting the composite for the BufferedImage to:
oImageGraphics.setComposite(AlphaComposite.SrcIn);
causes nothing to be shown. The Oracle documentation on compositing graphics in Java2D states this for 'SrcIn':
"If pixels in the source and the destination overlap, only the source pixels in the overlapping area are rendered."
So, I would expect this to have the same behavior I get with AlphaComposite.Src.
Maybe someone out there can shed some light on whats going on with these composites, and how I could achieve my desired effect.
There are a number issues with what you "seem" to be trying to do
Don't call getGraphics on a component. This can return null and only returns a snapshot of what was last painted during a Swing paint cycle. Anything you paint to it will be erased on the next paint cycle
You should also never dispose of Graphics context you did not create, doing so could effect other components that are painted by Swing
Painting is compounding, this means that painting to the same Graphics context (or BufferedImage) over and over again, will continue to apply those changes over the top of what was previously painted
You also don't seem to have a concept of how animation should work. Instead of trying to paint your fade effect in a single pass, where the results can't be applied to the screen, you need to apply a phase on each cycle and allow that to be updated to the screen before the next pass runs.
The following is a really basic example of what I'm talking about. It takes a "base" image (this could be the "base" state of the game, but I've used a static image) and the paints effects over the top.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Engine engine;
private Image frame;
public TestPane() {
engine = new Engine();
engine.setEngineListener(new EngineListener() {
#Override
public void updateDidOccur(Image img) {
frame = img;
repaint();
}
});
engine.start();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
engine.addEffect(new FadeOutEffect(Color.BLACK));
}
});
}
#Override
public Dimension getPreferredSize() {
return engine.getSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (frame != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(frame, 0, 0, null);
g2d.dispose();
}
}
}
public interface EngineListener {
public void updateDidOccur(Image img);
}
public class Engine {
// This is the "base" image, without effects
private BufferedImage base;
private Timer timer;
private EngineListener listener;
private List<Effect> effects = new ArrayList<Effect>(25);
public Engine() {
try {
base = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/megatokyo_omnibus_1_3_cover_by_fredrin-d4oupef 50%.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int width = base.getWidth();
int height = base.getHeight();
BufferedImage frame = new BufferedImage(width, height, base.getType());
Graphics2D g2d = frame.createGraphics();
g2d.drawImage(base, 0, 0, null);
Iterator<Effect> it = effects.iterator();
while (it.hasNext()) {
Effect effect = it.next();
if (!effect.applyEffect(g2d, width, height)) {
it.remove();
}
}
g2d.dispose();
if (listener != null) {
listener.updateDidOccur(frame);
}
}
});
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public void addEffect(Effect effect) {
effects.add(effect);
}
public void setEngineListener(EngineListener listener) {
this.listener = listener;
}
public Dimension getSize() {
return base == null ? new Dimension(200, 200) : new Dimension(base.getWidth(), base.getHeight());
}
}
public interface Effect {
public boolean applyEffect(Graphics2D context, int width, int height);
}
public class FadeOutEffect implements Effect {
private int tick = 0;
private Color fadeToColor;
public FadeOutEffect(Color fadeToColor) {
this.fadeToColor = fadeToColor;
}
#Override
public boolean applyEffect(Graphics2D context, int width, int height) {
tick++;
float alpha = (float) tick / 100.0f;
if (alpha > 1.0) {
return false;
}
Graphics2D g2d = (Graphics2D) context.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setColor(fadeToColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
return true;
}
}
}
Remember, every effect or change should be applied within the same "main loop", this means you shouldn't have multiple threads, in fact, since Swing is not thread safe, you should avoid having any additional threads if possible. This example make use of a Swing Timer to act as the "main loop" because the ActionListers actionPerformed method is called within the context of the EDT, making it safe to update the UI from. It also provides a simple synchronisation method, as the UI can't be painted while the actionPerformed method is been called

Java repaint() doesn't call the paint() in applet

To be clear, I've been researching for more than five hours now, I read all the related questions and more than 20 google searches, none of them worked for me and none of them described my case specifically.
First of all here's my code :
import java.util.*;
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import static Debug.StaticVar.*;
/*
<applet code="ImageTest" width=300 height=100>
</applet>
*/
public class ImageTest extends Applet {
Image img;
MediaTracker tracker;
public void init() {
tracker = new MediaTracker(this);
Thread Loader = new Thread(() -> {
img = getImage(getCodeBase(), "1.jpg");
tracker.addImage(img, 1);
});
Loader.setPriority(10);
Loader.start();
}
public void start() {
try {
tracker.waitForAll();
repaint();
} catch (InterruptedException e) {
}
}
public void paint(Graphics g) {
g.drawImage(img, 0, 0, null);
}
}
My Problem is the repaint method not calling paint method. To be more specific the paint method executes if I call repaint from another thread, or if I add the paint method to a child class and call repaint but it doesn't work in my code, where I directly call it from the applet main thread. Please HEEEEELP, I'm tired

How to force calling paintComponent?

I have a JLabel with the paintComponent() overriden.I want it to be forcefully called since the code that updates my Label UI is this event.How can I force its calling and updating of UI? (by the way,repaint does not work!)
here is my code :
BufferedImage background;
String Uri;
public CustomClockLabel(String Uri){
init(Uri);
this.Uri = Uri;
}
public void init(String Uri){
try {
URL inp = CustomClockLabel.class.getResource(Uri);
background = ImageIO.read(inp);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(background != null){
g2.drawImage(background, 0, 0,getWidth(),getHeight(), this);
}
g2.dispose();
super.paintComponent(g);
}
here is the code that updates labels and it is called recursively :
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
lblHour1.repaint();
lblHour2 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(1)+".png");
lblHour2.repaint();
lblMin1 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(0)+".png");
lblMin1.repaint();
lblMin2 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(1)+".png");
lblMin1.repaint();
You may be under the false impression that creating new labels will update what's on the screen, doing this...
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
lblHour2 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(1)+".png");
lblMin1 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(0)+".png");
lblMin2 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(1)+".png");
Will change the reference of the variables, so they will no longer be the same variables as those you added to the screen.
Assuming that the above variables have being added to the screen already, you could simply update them by using something like...
lblHour1.init(new ImageIcon("/icons/noa_en/"+Hour.charAt(0)+".png"));
lblHour2.init(new ImageIcon("/icons/noa_en/"+Hour.charAt(1)+".png"));
lblMin1.init(new ImageIcon("/icons/noa_en/"+Minute.charAt(0)+".png"));
lblMin2.init(new ImageIcon("/icons/noa_en/"+Minute.charAt(1)+".png"));
revalidate();
repaint();
If that fails, you should try setting one the labels border's properties so you can see if it's actually been added to the screen.
Updated
After some experimentation with what little you code you have made available, here are some more recommendations...
As has already being mentioned, make sure you are calling super.paintComponent first, as one of the jobs of this method is to clear the graphics ready for painting...
Make sure you provide a suitable sizing hint to the component, so the layout managers have some kind of idea of how big you might like the component to be. This ensures that the component is not sized to 0x0
The following example is very simple, but takes (what little) code you supplied and builds a runnable example from it...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PaintComponentTest {
public static void main(String[] args) {
new PaintComponentTest();
}
private int time = 0;
public PaintComponentTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final CustomClockLabel counter = new CustomClockLabel("/icons/0.png");
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
time++;
if (time > 9) {
time = 0;
}
counter.init("/icons/" + Integer.toString(time) + ".png");
counter.repaint();
}
});
timer.start();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(counter);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CustomClockLabel extends JPanel {
BufferedImage background;
String Uri;
public CustomClockLabel(String Uri) {
init(Uri);
this.Uri = Uri;
}
public void init(String Uri) {
try {
URL inp = getClass().getResource(Uri);
background = ImageIO.read(inp);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (background != null) {
g2.drawImage(background, 0, 0, getWidth(), getHeight(), this);
}
g2.dispose();
}
}
}
Calling repaint() on the component in question will force it to paint again.
The problem you have does not appear to repainting, as you are actually changing the labels on the panel. Make sure you remove the old labels and add the new ones instead and call revalidate(). (The code you posted looks like you are just updating the label references and not actually changing them out on the panel.)
Overall, the design could be improved dramatically by making your CustomClockLabel class take in a parameter that changes the image and therefore allows you to just call repaint().
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
The above code doesn't do anything. All is does is create a new component. But that component is not added to the GUI so it obviously doesn't repaint.
There is nothing in your class that would cause it to need repainting, so your question doesn't make sense. You have a design problem. I don't see any reason to create a custom label.
If you want to change the image, then just use a standard JLabel with an Icon. Then to change the image you just use the setIcon(...) method and the label will repaint itself automatically.
(by the way,repaint does not work!)
If it isn't, then the only thing i can suspect, the fault is in your painting order: where you are calling super.paintComponent(g); after you are drawing the image. If your label is non-opaque and has background color, then you will not see the image, as the later painting super.paintComponent(g) will be drawn above the previous painting.
Try changing the order:
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(background != null){
g2.drawImage(background, 0, 0,getWidth(),getHeight(), this);
}
g2.dispose();

JOptionPane.showMessageDialog() shows but without any message?

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.

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