6

Within my java application I have a JTabbedPane on a main JFrame with many JPanel tabs. Within the tabs I have a function

public void newStatus(final String arg)
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           mainView.newStatusLine(arg);
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

this function calls the main JFrame mainView function to write some text to a JTextPane. My issue is that this doesn't allow me to get a return value from the main JFrame. I would like to do something like

public InfoObj getInfo()
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           return mainView.getInfo();
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

But I am not sure how to make this work. I have tried to and followed my IDE's messages to see if I can get it to maybe work, but I cannot override Runnable.Run to return something.

Are there any mechanisms to do something like this?

EDIT: For HoverCraftFullOfEels, The overall problem is talking between JPanels themselves and between the main JFrame and a JPanel. At some point a JPanel wants to tell the main JFrame to do something, or it might want to get some data from it. But from everything I know I cannot just pass a reference of this to either the JFrame or JPanel and use it to call a public function or read some public field in either.

Sometimes I want to do this not on the EDT via a spawned thread. Some of the JPanel spawn threads and I would like to pass the JPanels reference to the main JFrame so it can call functions within it to tell the user something about what the thread is doing.

KDecker
  • 6,928
  • 8
  • 40
  • 81
  • This smells of a possible XY Problem. Better for you to tell us more the overall issue you're trying to solve and not how you're trying to solve it with code. Also why even queue a Runnable on the EDT here? I presume that this code is already being called from the EDT, no? – Hovercraft Full Of Eels Sep 03 '14 at 02:47
  • What part of **later** don't you understand? That code didn't run yet. – SLaks Sep 03 '14 at 02:49
  • Generally to return something you need [`Callable`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html). – PM 77-1 Sep 03 '14 at 02:54
  • 1
    Basically, you can't...what you can do is set up some kind of [observer pattern](http://www.oodesign.com/observer-pattern.html) – MadProgrammer Sep 03 '14 at 02:55
  • 1
    @PM77-1: but that wouldn't work if it were queued on the event thread. Then it would behave as a Runnable. – Hovercraft Full Of Eels Sep 03 '14 at 02:55
  • @HovercraftFullOfEels I've made an edit to try and explain better. // Maybe this might help to. The main `JFrame` has an `Inventory` that the tabs each edit in some way. So I might want to edit the `JFrame.Inventory` object from `JFrame.SortJPanel` so that other `JFrame.JPanel`s can see it. – KDecker Sep 03 '14 at 03:01
  • Then go with @MadProgrammer's recommendation of using an observer pattern. For instance, you could create a non-GUI model class, that holds the data that is displayed in the main method. The other classes could be passed a reference to this object, and then could change its state. The main could then be notified of changes by registering a listener on to the model object. – Hovercraft Full Of Eels Sep 03 '14 at 03:05
  • The other classes could also in turn be notified of changes via a listener? // What if at some point a class is trying to modify a field in the model that another class is currently writing? – KDecker Sep 03 '14 at 03:15
  • Any class that registers a listener with the model would be notified. Regarding your last point, if all is done on the Swing event thread, then this can't happen. – Hovercraft Full Of Eels Sep 03 '14 at 03:22
  • Is this a good example of the observer pattern? http://stackoverflow.com/a/6270150/2985796 // What if it is not happening on the EDT? – KDecker Sep 03 '14 at 03:30

3 Answers3

14

It looks like your application data is all bound up inside your user interface objects. If everything you do can be done on the EDT, then you should be OK. You can make direct calls among the objects that need information from each other. Since all of this is on the EDT, it's effectively single-threaded, and there are no race conditions or deadlocks. This, however, can couple various parts of the UI code to each other rather too tightly. In that case you can use some kind of observer or listener pattern as the commenters have suggested. This works fine in a single-threaded, event-driven environment like with AWT/Swing.

But then you say you might want to do this from some thread other than the EDT. That changes everything.

(As an aside, this is the sort of thing that causes people to consider having all your application data bound up inside your UI objects to be an anti-pattern. It makes it quite difficult to get to this data from outside the UI subsystem.)

You could add an observer pattern, but there are restrictions. The UI owns the data, so only the EDT can change the data and fire events to observers. The observers lists need to maintained in a thread-safe fashion. Presumably the observers will want to update data structures in response to events. These data structures will have to be thread-safe, since they're accessed both from the EDT and from your other thread. This approach will work, but you have to do a lot of thinking about which thread is calling which methods, and which data structures have to be made thread-safe.

Assuming you don't want to go this route, let's return to your original question, which was about how to return something from invokeLater. Doing this would let you keep your data in the UI while allowing other threads to get data out of the UI when they need it. It is possible to do this, with a bit of indirection. It does have risks, though.

Here's the code. This can be called from the "other" (non-EDT) thread.

InfoObj getInfo() {
    RunnableFuture<InfoObj> rf = new FutureTask<>(() -> getInfoObjOnEDT());
    SwingUtilities.invokeLater(rf);
    try {
        return rf.get();
    } catch (InterruptedException|ExecutionException ex) {
        ex.printStackTrace(); // really, you can do better than this
    }
}

The trick here is that RunnableFuture is an interface that is both a Runnable and a Future. This is useful because invokeLater takes only a Runnable, but Future can return a value. More specifically, Future can capture a value returned in one thread and store it until another thread wants to get it. FutureTask is a convenient, ready-made implementation of RunnableFuture that takes a Callable argument, a function that can return a value.

Basically we create a FutureTask and hand it a bit of code (the Callable, a lambda expression in this example) that will run on the EDT. This will gather the information we want and return it. We then post this to the EDT using invokeLater. When the other thread wants the data, it can call get() immediately or it can hang onto the Future and call get() later. There is a small inconvenience in that Future.get() throws a couple checked exceptions that you have to deal with.

How does this work? If the EDT runs the Callable first, the return value is stored in the Future until the other thread calls get() on it. If the other thread calls get() first, it's blocked until the value becomes available.

And there's the rub. Astute readers will recognize that this has the same risk as invokeAndWait(), namely, if you're not careful, you can deadlock the system. This can occur because the thread that calls get() might block waiting for the EDT to process the event posted by invokeLater. If the EDT is blocked for some reason -- perhaps waiting for something held by the other thread -- the result is deadlock. The way to avoid this is to be extremely careful when calling something that might block waiting for the EDT. A good general rule is not to hold any locks while calling one of these blocking methods.

For an example of how you can get yourself into trouble with invokeAndWait or with invokeLater(FutureTask), see this question and my answer to it.

If your other thread is entirely decoupled from the UI data structures, this technique should be quite effective.

Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • I think the answer to my question is in here. But it is not applied to somethign real-world for me to review it properly. I have asked a similar question here http://stackoverflow.com/questions/25649950/passing-data-between-swing-components-and-worker-threads if you would review it for me? – KDecker Sep 03 '14 at 17:05
1

I know this question is quite old, but the following should provide a handy workaround for the issue.

You'll need this class to be able to define a final object which encapsulates your return value:

public class Shell<T> {

    private T value;

    public T getValue() {
        return this.value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

Afterward, you can run the whole thing is a nested Runnable f.e. like this:

public InfoObj getInformation() {
    final Shell<InfoObj> returnObj = new Shell<InfoObj>();
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                returnObj.setValue(mainView.getInfo());
            }
        });
    } catch (InvocationTargetException | InterruptedException e1) {
        e1.printStackTrace();
    }
    return returnVal.getValue();
}

Note that I changed the method name from the question because this method is not meant to be called from within the Runnable. You can call this method from any thread and it will return you a value. However, I've also changed the invokeLater() to invokeAndWait(), because it can happen that the return value is null if the operation you execute in the Runnable has not assigned a value to the Shell object yet. If you don't want your thread to be blocked and use invokeLater() you can also return the Shell object to read it when it has a value.

User1337
  • 71
  • 2
  • 8
0

This works for me JAVA7

 //Here the main thread invokes it
    final RunnableFuture<Data> task = new FutureTask<Data>( new Callable<Data>() {
        @Override
        public Data call() throws Exception {
                  //Here EDT thread
                return calculateData();
           }
        });
        SwingUtilities.invokeLater(task);
        try {
        return task.get();
        } catch (InterruptedException | ExecutionException e) {
            logger.log(Level.SEVERE, "Exception", e);
        }
Bhabani pattanayak
  • 149
  • 1
  • 1
  • 9