0

I am trying to change the icon (background) of a JLabel, but I am having an issue with the icon not updating. Whenever I tried lblStatusImg.setIcon(new ImageIcon(Brix_Updater_Module.class.getResource("/resources/fail.png"))); to change the JLabel in the main method, the compiler was first complaining that the variable lblStatusImg did not exist, so I moved it from the JFrame initialization method to a class level variable. After this, Eclipse complained that I was trying to reference a nonstatic method from static context, so I made lblStatusImg static. This made it possible for the program to compile, but the icon did not change whenever it was supposed to.

Since it's kind of hard to understand my problem here is a download link for an Eclipse workspace that demonstrates my problem. When you first open it, you will notice that there are some problems with it. They were left there on purpose to make it easier for you to see where I am having a hard time. If Eclipse asks you to make the items in question static, just do it and then run the program. You'll notice that it does not change the label icons as it should.

Since not all of you have Eclipse, here's the entire code from the workspace.

import java.awt.Component;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;

import java.awt.Font;

import javax.swing.SwingConstants;

import java.awt.Window.Type;
import java.io.BufferedOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Timer;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.ImageIcon;
import javax.swing.JButton;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;


public class StackOverflow_Image_Resource_Demo {

    private JFrame frmUpdate;
    JLabel lblStatusImg = new JLabel("");
    JButton btnUpdateComplete = new JButton("OK");

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    StackOverflow_Image_Resource_Demo window = new StackOverflow_Image_Resource_Demo();
                    window.frmUpdate.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        try {
            lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("success.png")));
            btnUpdateComplete.setVisible(true);
        }
        catch(Exception e)
        {
            Component frame = null;
            lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("/resources/fail.png")));
            JOptionPane.showMessageDialog(frame, "Update Failed", "Update Failed", JOptionPane.ERROR_MESSAGE);
            System.exit(1);
        }
    }

    /**
     * Create the application.
     */
    public StackOverflow_Image_Resource_Demo() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frmUpdate = new JFrame();
        frmUpdate.setType(Type.UTILITY);
        frmUpdate.setTitle("StackOverflow Image Resource Issue Demo");
        frmUpdate.setBounds(100, 100, 450, 300);
        frmUpdate.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmUpdate.getContentPane().setLayout(null);

        //JLabel lblStatusImg = new JLabel(""); - Commented out when I made lblStatusImg class level.
        lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("/resources/updating.gif")));
        lblStatusImg.setBounds(10, 22, 414, 97);
        frmUpdate.getContentPane().add(lblStatusImg);

        //JButton btnUpdateComplete = new JButton("OK"); - Commented out when I made btnUpdateComplete class level.
        btnUpdateComplete.setVisible(false);
        btnUpdateComplete.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent arg0) {
                System.exit(1);
            }
        });
        btnUpdateComplete.setBounds(170, 179, 89, 23);
        frmUpdate.getContentPane().add(btnUpdateComplete);
    }
}

Here is a newer version of my code that updates the image, but doesn't fully load the UI until everything else is done.

import java.awt.Component;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;

import java.awt.Font;

import javax.swing.SwingConstants;

import java.awt.Window.Type;
import java.io.BufferedOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Timer;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.ImageIcon;
import javax.swing.JButton;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;


public class StackOverflow_Image_Resource_Demo {

    private JFrame frmUpdate;
    JLabel lblStatusImg = new JLabel("");
    JButton btnUpdateComplete = new JButton("OK");

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    StackOverflow_Image_Resource_Demo window = new StackOverflow_Image_Resource_Demo();

try { lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("success.png"))); btnUpdateComplete.setVisible(true); } catch(Exception e) { Component frame = null; lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("/resources/fail.png"))); JOptionPane.showMessageDialog(frame, "Update Failed", "Update Failed", JOptionPane.ERROR_MESSAGE); System.exit(1); } window.frmUpdate.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); }

    /**
     * Create the application.
     */
    public StackOverflow_Image_Resource_Demo() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frmUpdate = new JFrame();
        frmUpdate.setType(Type.UTILITY);
        frmUpdate.setTitle("StackOverflow Image Resource Issue Demo");
        frmUpdate.setBounds(100, 100, 450, 300);
        frmUpdate.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmUpdate.getContentPane().setLayout(null);

        //JLabel lblStatusImg = new JLabel(""); - Commented out when I made lblStatusImg class level.
        lblStatusImg.setIcon(new ImageIcon(StackOverflow_Image_Resource_Demo.class.getResource("/resources/updating.gif")));
        lblStatusImg.setBounds(10, 22, 414, 97);
        frmUpdate.getContentPane().add(lblStatusImg);

        //JButton btnUpdateComplete = new JButton("OK"); - Commented out when I made btnUpdateComplete class level.
        btnUpdateComplete.setVisible(false);
        btnUpdateComplete.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent arg0) {
                System.exit(1);
            }
        });
        btnUpdateComplete.setBounds(170, 179, 89, 23);
        frmUpdate.getContentPane().add(btnUpdateComplete);
    }
}
DaveTheMinion
  • 664
  • 3
  • 22
  • 45
  • I've never managed to successfully load an application resource from a static method. I suggest you move all that code within the constructor of the class and change `StackOverflow_Image_Resource_Demo.class.getResource("/resources/fail.png")` to `this.getClass().getResource("/resources/fail.png")`. BTW - why are `success.png` and `resources/fail.png` in different places? – Andrew Thompson Mar 24 '14 at 00:11
  • @AndrewThompson My two images are not supposed to be in different places. I must have made a mistake when writing that line. – DaveTheMinion Mar 24 '14 at 00:25

1 Answers1

3

Two things come to find. The first is, as you say, you're trying to reference a non-static variable from a static context.

The second is, you don't seem to understand how threading works...

Basically, main is typically executed within the "main" thread (when executed by the JVM).

You then use EventQueue.invokeLater. Which as, the name suggests, will execute the Runnable "later"...at some time in the future...

        EventQueue.invokeLater(new Runnable() {
            public void run() {

You then try and change the the icon (let's pass over the non-static reference for a momement)...but lblStatusImg won't have been initialized nor is it likely to have been displayed, as the Runnable has not yet been executed, meaning, even if you didn't run into a NullPointerException, you won't see the change...

You can test by adding a System.out in your Runnable and before the first lblStatusImg.setIcon call in the main method.

What you should do is...

  1. Move the "status" change change to within the Runnable context.
  2. Provide a setStatus method that is capable of changing the label and UI content as required based on the provide status

For example...

public static final int SUCCESS = 0;
public static final int FAIL = 0;

//...

public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    StackOverflow_Image_Resource_Demo window = new StackOverflow_Image_Resource_Demo();
                    // This e
                    window.frmUpdate.setVisible(true);

                    window.setStatus(StackOverflow_Image_Resource_Demo.SUCCESS);
                } catch (Exception e) {
                    e.printStackTrace();
                    Component frame = null;
                    window.setStatus(StackOverflow_Image_Resource_Demo.FAIL);
                    JOptionPane.showMessageDialog(frame, "Update Failed", "Update Failed", JOptionPane.ERROR_MESSAGE);
                    window.dispose();
                }
            }
        });
    }

You should avoid exposing instance fields as public and instead, provide methods that either change their state indirectly (such as setStatus) or directly (setStatusIcon). In this case, I prefer the first method as this allows the class to determine what a change in status actually means.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Can I remove the code contained within `EventQueue.invokeLater(new Runnable() {` into the main method? – DaveTheMinion Mar 23 '14 at 23:14
  • @DavidB NO, absolutely not. The code outside of the `invokeLater` is in violation of the single thread rules of Swing and belongs inside the `invokeLater` – MadProgrammer Mar 23 '14 at 23:18
  • It's apparent that I have no idea what the heck I am doing. Could you give me an example of a way to properly do what I am seeking? I understand the problem with the main code executing before the invoke later stuff, but I do not know how I would solve it. – DaveTheMinion Mar 23 '14 at 23:22
  • From what I understand of your explanation, I should create a method inside `invokeLater` and then call it when I want to change the UI. I have done this, but Eclipse says that the method does not exist. – DaveTheMinion Mar 23 '14 at 23:33
  • Then you didn't create it (or least create it within a context that Eclipse knows about). I suggested creating a method in `StackOverflow_Image_Resource_Demo` which would require and instance for it to be called... – MadProgrammer Mar 23 '14 at 23:35
  • So do you mean make a method outside of `invokeLater` and then call it from within `invokeLater`? – DaveTheMinion Mar 23 '14 at 23:39
  • The method should be a method of the `StackOverflow_Image_Resource_Demo` class, just like `initialize`, expect it should be `public`. You would then only call this from within the context of the EDT – MadProgrammer Mar 23 '14 at 23:43
  • I was hoping [this](http://stackoverflow.com/questions/7896723/how-do-you-use-the-event-dispatch-thread) would help, but it didn't. How do you use the EDT? – DaveTheMinion Mar 23 '14 at 23:53
  • What do you mean by "use the EDT"? – MadProgrammer Mar 23 '14 at 23:59
  • How do I call things from within the EDT? – DaveTheMinion Mar 24 '14 at 00:00
  • Once you're inside the `run` method, you're in the EDT, that's the point of using `invokeLater`, this guarantees that the `run` method is been run from within the context of the EDT – MadProgrammer Mar 24 '14 at 00:05
  • Thanks MadProgrammer! You're a genius! I moved all of my code into `run()`, and it works! Thanks you! – DaveTheMinion Mar 24 '14 at 00:20
  • It works with a catch: the UI doesn't load until the program either fails or succeeds. How can I resolve this? – DaveTheMinion Mar 24 '14 at 00:30
  • No idea, how do you determine a failed/success state? – MadProgrammer Mar 24 '14 at 00:32
  • I am using a try..catch statement. If the update is successful, the icon changes to tell so and if the update fails, the catch statement changes the icon to tell so. The way the program runs now, I have an empty box that loads on the screen until the update either succeeds or fails, at which point everything finally loads. I've added the altered code to my question. – DaveTheMinion Mar 24 '14 at 00:39
  • Sounds like you want to take a look at [`SwingWorker`](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html) – MadProgrammer Mar 24 '14 at 00:42
  • I moved my code into a `SwingWorker` called `downloadWorker`, and called it in `run()` using `downloadWorker.run();`. This warranted the same results as before with the partially loaded window. – DaveTheMinion Mar 24 '14 at 00:54
  • You should be calling `execute`, not `run`. The only code that needs to be in the `SwingWorker#doInBackground` method is that code that DOES NOT interact with the UI. You would need to then override `done` which will tell you when `doInBackground` has completed (but is called from within the context of the EDT), call `get`, which will throw the `Exception` which was raised from `doInBackground` if any or return the value that `doInBackground` returned... – MadProgrammer Mar 24 '14 at 00:59
  • Thanks. Using execute instead of run made everything work just fine. The only problem that I have now is that I don't understand what all that stuff you wrote there means. I would appreciate an explanation, but since the goal has been achieved, I'm marking this as accepted. – DaveTheMinion Mar 24 '14 at 01:21
  • Best thing is to read the tutorial. Basically, `SwingWorker` provides you with the ability to run long running tasks of the EDT, but provides mechanisms for synchronising updates back to the EDT for safe updates – MadProgrammer Mar 24 '14 at 01:36
  • I will read through the tutorial and be sure to bother you if there is something that I do not understand. `:)` – DaveTheMinion Mar 24 '14 at 01:55