4

I tried this for many hours.. I have a thread that changes a JTextField of my UI, which completely destroys the UI. The Thread (lets call it Thread A) is generated by an ActionListener. The .setText() function call is in a extra thread (B) created by Thread A. Thread B is the Parameter of SwingUtilitis.invokeAll() and/or SwingUtilities.invokeAndWait(). I tried them both. Here's some code to make it more clear.

This is my ActionListener which creates Thread A - shortened of course:

public void actionPerformed(ActionEvent evt) {
    Object source = evt.getSource();
    if (source == window.getBtn_Search()) {
        Refresher refresh = new Refresher();
        refresh.start();
    }
}

This is my Thread A, which later puts Thread B into the EDT Queue:

public class Refresher extends Thread implements Runnable {

private int counter = 0;
private UI window = null;
private int defRefresh = 0;

@Override
public void run() {
    while(true){
        -bazillion lines of code-
                do {
                    try {
                        Refresher.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(window.canceled()) break;
                    UI.updateCounter(window.getLbl_Status(), (Configuration.getRefreshTime()-counter));
                    counter++;
                } while (counter <= Configuration.getRefreshTime());
             - more code-
    }
}
}

The UI.updateCounter(...) will queue Thread B into the EDT.

public static void updateCounter(final JLabel label, final int i) {
    try {
        SwingUtilities.invokeAndWait( 
            new Runnable() {
                public void run() {
                    label.setText("Refreshing in: " + i + " seconds.");
                }
            }
        );
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Now when the last function gets called, everything gets messed up. I tried different stuff for hours and nothing worked. I also tried using SwingWorker, but the some or nothing at all happened.

Zoyd
  • 3,449
  • 1
  • 18
  • 27
  • And...what gets *"messed up"*? – MadProgrammer Dec 23 '12 at 03:02
  • I have a JPanel which contains multiple smaller JPanels, these contain an icon and multiple JLabels. When the function gets called these JLabels appear in different places, some disappear completely. The Label window.getLbl_Status() has nothing to do with these and are somewhere completely else. The Icon also changes positions – user1924422 Dec 23 '12 at 03:05
  • 2
    The change to the value of your `label` may be effecting the layout of it's container and the containers around it – MadProgrammer Dec 23 '12 at 03:07
  • Is there some workaround? It seems weird since I pretty much just called setText() – user1924422 Dec 23 '12 at 03:12
  • 1
    You need to provide the label with enough room that when its text changes, it doesn't want to bully the rest of components. A cheeky way is to use a none editable text field. Remove its board and make transparent. – MadProgrammer Dec 23 '12 at 03:15
  • 2
    I wonder if you wouldn't be better served by using a JTable and then updating its model rather than a bunch of JLabels. Consider telling and showing us more about your problem. – Hovercraft Full Of Eels Dec 23 '12 at 03:18
  • Sadly thats not possible, since the UI isnt a table at all. But I finally made a workaround. I just created a new Label, got all the attributes of the old one, removed the old one and added the new one to the panel. – user1924422 Dec 23 '12 at 03:33
  • It is not threads that you enqueue on the EDT, but runnables. The EDT is the thread that will run them. – flup May 21 '14 at 21:43

4 Answers4

1

The invokeAndWait() tried allows to post a Runnable task to be executed on the EDT, but it blocks the current thread and waits until the EDT is done executing the task.

But there is deadlock potential in invokeAndWait(), as there is in any code that creates a thread interdependency. If the calling code holds some lock (explicitly or implicitly) that the code called through invokeAndWait() requires, then the EDT code will wait for the non- EDT code to release the lock, which cannot happen because the non-EDT code is waiting for the EDT code to complete, and the application will hang.

As we can see here, modifying the JLabel component passed by the waiting non- EDT code.

Instead we can use

invokeLater() takes care of creating and queuing a special event that contains the Runnable. This event is processed on the EDT in the order it was received, just like any other event. When its time comes, it is dispatched by running the Runnable’s run() method.

SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Refreshing in: " + i + " seconds.");
}
});

OR

isEventDispatchThread() that returns true if the calling code is currently being executed on the EDT, false otherwise.

Runnable code= new Runnable() {
                public void run() {
                    label.setText("Refreshing in: " + i + " seconds.");
                }
            }
        );

if (SwingUtilities.isEventDispatchThread()) {
code.run();
} else {
SwingUtilities.invokeLater(code);
}
TiyebM
  • 2,684
  • 3
  • 40
  • 66
0

In general, labels are not very good at displaying text which change: their width change, and the layout with it.

Using a read-only JTextField, perhaps with proper changes in style, could be a better solution.

khaemuaset
  • 217
  • 1
  • 9
0

I think the intermediate JPanels you've created may count as validation roots. Therefore the revalidate() that automagically happens when you call setText() does not cause any layout changes higher than the level of the JPanel parent.

I don't think you actually need the panels, since a JLabel can contain both an Icon and text. See the tutorial.

So my advice is to remove the panels or, if they serve a purpose, make sure isValidateRoot() on the panels returns false.

flup
  • 26,937
  • 7
  • 52
  • 74
-1

When changing the label's text you should at least call repaint()/revalidate() on the label's topmost container, triggering a relayout, assuming the label calls invalidate()/revalidate() correctly on text change.

stryba
  • 1,979
  • 13
  • 19
  • No, the setText() will trigger a revalidate() *and* a repaint(). There is no need to do so yourself. See http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/javax/swing/JLabel.java#JLabel.setText%28java.lang.String%29 – flup May 22 '14 at 07:16
  • Read again, what I wrote: "assuming the label calls invalidate()/revalidate() correctly on text change", I never said this should be done by yourself – stryba May 22 '14 at 09:47
  • Revalidating the topmost container makes no sense since the label and its parent panel have already been through a revalidate and will claim to be valid. And what will repainting everything achieve if the label text has already been repainted, albeit on a panel with the wrong size? It'll simply repaint everything exactly as it was. You *could* advise to call invalidate on the label and its grandparent and then validate on its grandparent. Though even then I'd call it a workaround. – flup May 22 '14 at 10:15
  • 1
    Just thought about it, and you are right. Don't know what I thought at that time. – stryba May 22 '14 at 11:13