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 JPanel
s reference to the main JFrame
so it can call functions within it to tell the user something about what the thread is doing.
An invokeLater() method is a static method of the SwingUtilities class and it can be used to perform a task asynchronously in the AWT Event dispatcher thread. The SwingUtilities. invokeLater() method works like SwingUtilities. invokeAndWait() except that it puts the request on the event queue and returns immediately.
Their only difference is indicated by their names: invokeLater simply schedules the task and returns; invokeAndWait waits for the task to finish before returning. You can see examples of this throughout the Swing tutorial: SwingUtilities. invokeLater(new Runnable() { public void run() { createAndShowGUI(); } });
SwingUtilities class has two useful function to help with GUI rendering task: 1) invokeLater(Runnable):Causes doRun. run() to be executed asynchronously on the AWT event dispatching thread(EDT). This will happen after all pending AWT events have been processed, as is described above.
Stores the position and size of the inner painting area of the specified component in r and returns r .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With