views:

127

answers:

4

*I'm using Java.

I have this thread, agent, that explores a room to determine its size and clean it if it's dirty. Then I have the interface, which draws the agent as it explores the environment. Agent is subclassed from Thread, and Java takes care of managing the threads. All I do is create the thread and say object.start().

This works very well under normal circustances. However, the menus are enabled during this time, and that means the user can mess with the room while the agent is exploring it. That shouldn't happen.

So, once the user clicks the option that tells the agent to clean the room, I'd like to disable all the menus. Except this isn't working as it should. Here's the problem:

...
public void ActionPerformed(ActionEvent e)
{
    //disable the menus with setEnabled(false);
    agent.start();
    //enable the menus with setEnabled(true);
}

The problem is that the menus are enabled before the agent thread executes its function. I thought about using Thread.join() - that would garantee that the code to enable the menus is only executed after the agent thread ends. But, if I use Thread.join() the interface doesn't update itself while the agent is moving, because it's waiting for the agent to finish!

I've thought about disabling the interface from the agent and then enabling it once the agent is done, but I'm not sure that would work and the biggest problem here is that the agent shouldn't be messing around with the menus.

So, to sum it up, I need a thread executing to update the interface/draw the agent moving, but that thread cannot be the same that enables the menu. It seems currently there is one thread doing both. Assuming that's possible and not too complicated.

+1  A: 

In your actionPerformed method it'd be fine to just disable the menus and then start the thread - but you shouldn't be enabling the menus again in the same function. Rather, you'll need to trigger the enabling of the menus when the thread finishes. I would suggest, at the end of your agent thread, calling SwingUtilities.invokeLater()) with a Runnable that will reenable the menus.

David Zaslavsky
+5  A: 

I've thought about disabling the interface from the agent and then enabling it once the agent is done, but I'm not sure that would work and the biggest problem here is that the agent shouldn't be messing around with the menus.

You could make the agent call a method in the GUI class that re-enables the menus, when it is done. The method would, of course, call the appropriate Swing methods on the EDT.

class Agent extends Thread
{
    @override
    public void run()
    {
       // run around the room

       // finally done
       gui.agentIsDone();
    }
}

class GUI extends JFrame
{
    ...

    void agentIsDone()
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @override 
            public void run()
            {
                menus.setEnabled(true);
            }
        });
    } 
}

If multiple agents can run concurrently, you'll need to check if all of them are done, before re-enabling the menu.

Zach Scrivena
just what i was looking for (clean interaction from worker thread to GUI)
Frederic Morin
+2  A: 

It sounds like you need your agent to issue notification (Does anyone still use Observer/Observable?) to your interface that cleaning has begun and finished. Your interface would, respectively, call setEnabled(false) and setEnabled(true) when it receives those notifications.

EDIT: The idea is to decouple the manipulation of the menu from the execution of the agent. I.e. what happens if you introduce another way to initiate cleaning, aside from the menu item? You probably still want the menu to be disabled in that case.

JMD
I hope people haven't been using Observer/Observable. That may be wishful thinking. They should have a big @deprecated on them.
Tom Hawtin - tackline
Fair enough. :) I was more trying to point out the need for the Agent to be giving notification of start/stop, rather than having the GUI know implicitly what the agent's activities are.
JMD
A: 

disable the menus before starting the thread. The thread needs to invoke some method on your controller when it's done, and in that method you enable the menus again. The agent won't be messing with menus; it will be just notifying the controller when it's done. The controller will enable the menus when it knows that the agent is done.

Also, supposedly it's not good practice to subclass Thread. You should instead implement Runnable and then create a Thread with your Runnable as its target, and start the thread. This way, if your agent is a Runnable, you can run it in its own thread, or in the main thread (by simply calling run()) or in a thread pool, etc.

Chochos