简体   繁体   中英

How to call a heavy algorithm from JButton ActionListener

I'm writing a Java program that acts as both a server and a client. Leaving out the irrelevant bits it has three classes: Main, Server and Client. Main just sets up a menu and contains the main method. Server and Client hold the algorithms for the server and the client respectively.

What I'm trying to do is to call the algorithm from the server and client classes and their GUIs depending on the button pressed. The code to call the server currently looks like this:

serverButton = new JButton();
serverButton.addActionListener( new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        server.showGUI();
        server.run();
    }
});

The problem is that server.run() runs continuously for quite a long while and is a lot of heavy lifting. This bugs out the GUI, which from my understanding is because I'm calling the method from the EDT.

How can I call this method from the main thread? Do I need to create a SwingWorker and leave it there until the end of server.run()?

How can I call this method from the main thread?

This is how it is usually done in Swing.

public class WhatEverServer {

    private UserInterface userInterface;
    [...]

    private static void createAndShowGUI() {

      if( GraphicsEnvironment.isHeadless() )
        logger.log( Level.FATAL, "This system seems to be 'headless'. Aborting now." ); 
      else {
        userInterface = UserInterface.getInstance();
        userInterface.createAndShowUI();
      }
    }

    public static void main( String[] args ) {

        // schedule a job for the event-dispatching thread:
        // creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater( new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}


public class UserInterface {

    ...
    public void createAndShowUI() {

      // make sure we have nice window decorations.
      JFrame.setDefaultLookAndFeelDecorated(true);

      UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName() );

      // create and set up the window.
      JFrame frame = new JFrame( "Whatever Server" );
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      // set UI components, i.e

      // set main menu bar
      frame.setJMenuBar( this.mainMenuBar );

      // set layout
      frame.getContentPane().setLayout( new BorderLayout() );

      // add UI components

      // display the window.
      frame.pack();
      frame.setVisible(true);
    }
}

This bugs out the GUI, which from my understanding is because I'm calling the method from the EDT.

Yes, since the action is triggered by an event, the actionPerformed() is invoked by (or on) the EDT. I don't know what you are doing in server.run(), but I suppose this should not end up on the EDT.

Do I need to create a SwingWorker and leave it there until the end of server.run()?

I would use SwingWorker or SwingUtilities in that case. You can write an ActionHandler in this way, using two threads, one for doing some of the 'heavy lifting', one for setting up the UI :

public void actionPerformed(ActionEvent e) {

  new Thread(new Runnable {
    public void run() {
      ...
      // do some 'heavy lifting' here ...

      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          server.setupUI();
        }  
      )
      ...
      // or do some 'heavy lifting' here
    }); 
  }
}

Make sure the server object reference is final and then invoke the method in a new thread in your actionPerformed method.

Runnable task = () -> {server.run();};
Thread thread = new Thread(task);
thread.start();

It depends on your requirement, if you want the user do not want anything to do until server returns, it is best to do it in a Busyindicator like :

public void actionPerformed( ActionEvent e )
{

    BusyIndicator.showWhile(Display.getCurrent(), new Runnable() 
    {
        @Override
        public void run() 
        {
           server.run();
        }
    });
}

This will show user a hour glass while the server run is going on and user is blocked from using UI.

Or

if you want the UI to be responsive, you need to call server.run() in a separate thread.

MyThread t = new MyThread()
{
   public void run()
   {
      server.run();
   }
}
t.start();

and it is good practice to add a listener to thread to notify completion of server response so UI can do its things.

t.addListener( new MyThreadListener()
{
   public void serverDone()
   {
       Display.getDefault().asyncExec( new Runnable()
       {
           public void run()
           {
           }
       });
  }
});

Please note this is not complete code for thread listener, just for idea sake.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM