Search code examples
javaswinguser-interfaceserial-portxbee

Problems using javax.swing.SwingWorker while trying to connect to a Serial Port


I'm developing an application, in Java, that uploads data received from an Xbee device to an online database. The functionality is there and everything works, but when developing a GUI (with WindowsPro Builder), everything goes sour. The thing is, the app needs to be able to detect the Xbee device and if not connected, wait for it to be connected - indefinitely (or until closed). I already have a method that detects if the serial port is an Xbee device; like I said, everything works, except when I integrate it with the GUI.

The problem I'm having is creating a GUI that first detects if an Xbee device is connected, and if not, display a message notifying the user to connect the device in order to continue. At the same time (while displaying the message), I need to be calling the method that scans serial ports. I'm thinking threads, but I haven't used those in a while. Please note that the method already ran once, when the application started, in order to try and connect. I've no code to show, since all I did was put up the basic frames, buttons, etc (no events yet). I'm new to GUI programming in Java, any suggestions?

Update: So I kinda tried to implement what MadProgrammer suggested, which I'm kinda convinced it works given the SwingWorker tutorial, but nothing happens.The only thing that runs is setting up the GUI. Up next is the code:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.SwingWorker;


public class ConnectionWorker extends SwingWorker<Boolean, String> {

private ConnectionListener callBack;
private Xbee xbee = new Xbee();
private QboComm comm;

public ConnectionWorker(ConnectionListener listener, QboComm comm) {
    this.callBack = listener;
    this.comm = comm;
}


protected void process(List<String> chunks) {
    String msg = chunks.get(chunks.size() - 1);
    if (msg.equals("WAITING")) {
        comm.publishError();
    }
}

protected Boolean doInBackground() throws Exception {
    boolean isConnected = false;
    this.xbee = QboComm.xbee;
    ArrayList<String> list = new ArrayList<String>();
    if(!isConnected){
        publish("WAITING");
        while(!isConnected){
            list = xbee.getSerialPorts();
            for(String s : list){
                isConnected = xbee.connect(s);
                if(isConnected){
                    publish("DONE");
                    break;
                }
            }
        }
    }
    return isConnected;
}


protected void done() {
    boolean check;
    try {
        check = get();
        if (check) {
            comm.removeError();
            callBack.connectionEstablished();
        } else {
            callBack.connectionFailed();
        }
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}  
}

import java.awt.EventQueue;


public class QboComm {

private JFrame frmQboCommunicator;
public static Xbee xbee = new Xbee();
private JInternalFrame internalFrame;
private JLabel lblConnected;

/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            QboComm window = new QboComm();
            window.frmQboCommunicator.setVisible(true);
            ConnectionListener callback = new ConnectionListener() {
                public void connectionEstablished() {
                    try {
                        ArrayList<String> list = new ArrayList<String>();
                        list = xbee.fetch();
                        DBConnector db = DBConnector.getConnector();
                        if(db.connect()){
                            for(String s : list){
                                db.upload(s.substring(0, 5), s.substring(5));
                            }
                        }
                        db.disconnect();
                        xbee.printDBList();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } 
                }
                public void connectionFailed() {
                    //IMPLEMENT
                }
            };
            new ConnectionWorker(callback, window).execute();
        }
    });
}

/**
 * Create the application.
 */
public QboComm() {
    initialize();
}

/**
 * Initialize the contents of the frame.
 */
private void initialize() {
    frmQboCommunicator = new JFrame();
    frmQboCommunicator.setTitle("Qbo Communicator");
    frmQboCommunicator.getContentPane().setBackground(new Color(165, 42, 42));
    frmQboCommunicator.getContentPane().setForeground(new Color(211, 211, 211));
    frmQboCommunicator.setBounds(100, 100, 450, 300);
    frmQboCommunicator.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frmQboCommunicator.getContentPane().setLayout(null);

    /**
     * Connection Error Pop-up
     */
    this.internalFrame = new JInternalFrame("Connection Error");
    internalFrame.setBorder(new LineBorder(new Color(153, 180, 209), 3));
    internalFrame.setBounds(103, 46, 227, 157);
    frmQboCommunicator.getContentPane().add(internalFrame);
    internalFrame.getContentPane().setLayout(null);
    JTextPane txtpnAQboCommunicator = new JTextPane();
    txtpnAQboCommunicator.setText("A Qbo Communicator is not connected. Please connect a Qbo Communicator to continue.");
    txtpnAQboCommunicator.setEditable(false);
    txtpnAQboCommunicator.setBounds(0, 0, 211, 128);
    internalFrame.getContentPane().add(txtpnAQboCommunicator);
    internalFrame.setVisible(false);

    /**
     * Application Name
     */
    JLabel lblQboCommunicator = DefaultComponentFactory.getInstance().createTitle("QBO COMMUNICATOR");
    lblQboCommunicator.setForeground(new Color(255, 255, 255));
    lblQboCommunicator.setBackground(new Color(255, 255, 240));
    lblQboCommunicator.setHorizontalAlignment(SwingConstants.CENTER);
    lblQboCommunicator.setBounds(144, 0, 146, 14);
    frmQboCommunicator.getContentPane().add(lblQboCommunicator);

    /**
     * Connected label, displayed when connected to Xbee device
     */
    this.lblConnected = DefaultComponentFactory.getInstance().createLabel("CONNECTED");
    lblConnected.setForeground(new Color(255, 255, 255));
    lblConnected.setFont(new Font("Tahoma", Font.BOLD, 14));
    lblConnected.setHorizontalAlignment(SwingConstants.CENTER);
    lblConnected.setBounds(144, 25, 146, 14);
    frmQboCommunicator.getContentPane().add(lblConnected);

    /**
     * Scroll Panel that displays uploaded data
     */

    JScrollPane scrollPane = new JScrollPane();
    scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    scrollPane.setBounds(53, 65, 344, 131);
    frmQboCommunicator.getContentPane().add(scrollPane);

    JPanel panel = new JPanel();
    panel.setBorder(new LineBorder(new Color(255, 255, 255)));
    panel.setBackground(new Color(165, 42, 42));
    scrollPane.setViewportView(panel);

    /**
     * Progress Bar
     */
    JProgressBar progressBar = new JProgressBar();
    progressBar.setStringPainted(true);
    progressBar.setFont(new Font("Tahoma", Font.BOLD, 12));
    progressBar.setBackground(new Color(255, 255, 255));
    progressBar.setForeground(new Color(255, 140, 0));
    progressBar.setBounds(53, 214, 344, 25);
    frmQboCommunicator.getContentPane().add(progressBar);
}

public void publishError(){
    this.internalFrame.setVisible(true);
    this.lblConnected.setText("DISCONNECTED");
}

public void removeError(){
    this.internalFrame.setVisible(false);
    this.lblConnected.setText("CONNECTED");
}
}

The second block of code is the main class which launches the application. Any suggestions?


Solution

  • I would strongly recommend the use of something like a SwingWorker.

    What you don't want to do is do anything on the Event Dispatching Thread that will block the UI, this will prevent you from updating the UI (including showing messages) and the UI from updating it self, making it look like it's hung.

    Check out Concurrency in Swing for more details.

    Swing is an event driven environment (as I'm sure), basically, you want your application to simply run until a connection is established, may showing a message window of some kind that states that it is waiting for a connection.

    I would be a relativlty simple process to setup a SwingWorker that could provide callbacks to another class.

    import java.awt.EventQueue;
    import java.util.List;
    import javax.swing.SwingWorker;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class WaitForSwingWorker {
    
        public static void main(String[] args) {
            new WaitForSwingWorker();
        }
    
        public WaitForSwingWorker() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    ConnectionListener callback = new ConnectionListener() {
    
                        @Override
                        public void connectionEstablished() {
                            // Continue running the app
                        }
    
                        @Override
                        public void connectionFailed() {
                            // Show error message :(
                        }
                    };
    
                    new ConnectionWorker(callback).execute();
    
                    // Execution will continue here...
    
                }
            });
        }
    
        public interface ConnectionListener {
            public void connectionEstablished();
            // If possible, you should provide a message as to why...
            public void connectionFailed();
        }
    
        public class ConnectionWorker extends SwingWorker<Void, String> {
    
            private ConnectionListener callBack;
    
            public ConnectionWorker(ConnectionListener listener) {
                callBack = listener;
            }
    
            @Override
            protected void process(List<String> chunks) {
                // Back in the EDT...
                String msg = chunks.get(chunks.size() - 1);
                if (msg.equals("WAITING")) {
                    // Show waiting for connection message...
                }
            }
    
            @Override
            protected Void doInBackground() throws Exception {
                // Within our own thread
                // Establish connection...
                if (!isConnected) {
                    publish("WAITING");
                    // Wait for incoming connection, this can block...
                }
                return null;
            }
    
            @Override
            protected void done() {
                // Back in the EDT
                if (isConnected) {
                    // Show failed to connect message?
                    callBack.connectionEstablished();
                } else {
                    callBack.connectionFailed();
                }
            }
    
        }
    
    }
    

    nb- This is a proof of concept, you'll need to fill in the functionality