简体   繁体   中英

Java Swing GUI blocked by method only when method is called on button click

I am creating an IRC bot in Java. I made a class called "loginHandler" which asks the user to input the login info, such as nick/pass/server/etc... When the user is done, the "Connect" button is clicked, which initializes the IRC handler class, bot . See the code:

package irc_bot;

import java.awt.BorderLayout;
//more imports are here but they are irrelevant right now

public class loginHandler extends JFrame {

private static final long serialVersionUID = 6742568354533287421L;

private bot irc_bot;

private String username;
private String password;
private int[] ports={80,6667};
//.....

//gui vars
private final JTextField usernameInput;
private final JTextField passwordInput;
//......

public loginHandler(){

    super("Login data");

    Panel = new JPanel(new BorderLayout());
    Panel.setLayout(new GridLayout(13, 1));

    JLabel label = new JLabel("Hover over labels for information!!");
    Panel.add(label);

    label = new JLabel("Username: ");
    label.setToolTipText("Type in your username!");
    Panel.add(label);
    usernameInput=new JTextField("");
    usernameInput.setEditable(true);
    Panel.add(usernameInput);

    label = new JLabel("Password: ");
    label.setToolTipText("Type in your password! Starts with 'oauth:'");
    Panel.add(label);
    passwordInput=new JPasswordField("");
    passwordInput.setEditable(true);
    Panel.add(passwordInput);

    //.......
    //the other textfields are here but they are irrelevant right now


    //The important part:
    JButton okButton=new JButton("Connect");
    okButton.addActionListener(
            new ActionListener(){
                public void actionPerformed(ActionEvent event){
                    //set data method
                    setData();
                    dispose();
                    //bot object:
                    try {
                        irc_bot=new bot(username, password, master_channel, master_admin);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //initiate  the bot:
                    try {
                        irc_bot.initiate(server, ports);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        );

    add(okButton, BorderLayout.SOUTH);


    add(new JScrollPane(Panel), BorderLayout.NORTH);

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300,400);
    setVisible(true);

}
private void setData(){
    username=usernameInput.getText();
    password=passwordInput.getText();
    server=serverInput.getText();
    master_channel=master_channelInput.getText();
    master_admin=master_adminInput.getText();
    port=portsInput.getText();

    //set up the ports: TO-DO
}

}

My problem is that the bot.initiate() method blocks the GUI of the bot object. When the bot.initiate() is not called, the GUI works as intended. When the bot.initiate() stops, the GUI will be functional again. The problem is that the initiate() method contains an infinite loop which reads the lines from the irc server (this is in irc_bot.mainMethod() ).

The GUI window shows up, but it is blank, and it does not respond to me trying to close it or anything else.

The program is actually not frozen, I can still communicate with the bot via irc.

The weird thing is that if I initiate the bot object in main() for example, it works as intended, the initiate() method does not block the gui.

Take a look at the bot class (I copied only the relevant parts):

package irc_bot;

//import stuff

public class bot {

//gui vars
private JFrame mainWindow;
//.....


//irc variables
private String server;
//.....

//lists
private List<String> botadminlist;
//.......

//for communicating with IRC
private Socket socket;
private BufferedWriter writer;//write to IRC
//.....
pritate bot_msg_handler handler;

private String line;//the received line from the IRC server is stored in this

//-----methods-----
//constructor: does the basic setup
public bot(String nick, String password, String master_channel, String master_admin) throws Exception {


    //gui stuff
    mainWindow=new JFrame("irc_bot");

    //setup the menu
    menuBar = new JMenuBar();

    //first menu
    menu = new JMenu("Commands");
    menu.setMnemonic(KeyEvent.VK_C);
    menuBar.add(menu);
    //ide jön a menü kifejtése
    menuItem = new JMenuItem("Show/Edit commands",
            KeyEvent.VK_S);
    //event handling
    menuItem.addActionListener(
            new ActionListener(){
                public void actionPerformed(ActionEvent event){
                    showCommands();
                }
            }
        );
    menu.add(menuItem);
    menuItem = new JMenuItem("Add command",
            KeyEvent.VK_A);
    //event handling
    menuItem.addActionListener(
            new ActionListener(){
                public void actionPerformed(ActionEvent event){
                    addCommand();
                }
            }
        );
    menu.add(menuItem);
    //more menus.......

    //more GUI elements

    //.......
    //setup the window
    mainWindow.add(bottomPanel,BorderLayout.SOUTH);
    mainWindow.add(menuBar, BorderLayout.NORTH);
    mainWindow.add(new JScrollPane(textBox), BorderLayout.CENTER);

    mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    mainWindow.setSize(800,600);
    mainWindow.setVisible(true);

    sendBotMsg=false;

    //setup lists
    modlist=new ArrayList<mod_element>();
    //.....

    //user settings
    this.nick=nick;
    this.password=password;
    this.master_channel=master_channel;
    this.master_admin=master_admin;

    //create the message handler object
    handler = new bot_msg_handler(this.nick, this.master_channel, this.master_admin, modlist,
            botadminlist, stafflist, adminlist, active_channels, cooldown, commands);
    handler.setTextBox(textBox);

    textBox.append("Constructor setup complete\n");
}//constructor

//IRC SPECIFIC STUFF
public void initiate(String server, int... ports) throws Exception{
    this.server=server;
    if(connect(server, ports)==-1){
        JOptionPane.showMessageDialog(null, "Bot couldn't connect to the server!");
        mainWindow.dispose();
        return;
    }
    if(logon()==-1){
        JOptionPane.showMessageDialog(null, "Bot couldn't log in!");
        mainWindow.dispose();
        return;
    }
    join(master_channel);

    mainMethod();

    }

private int connect(String server, int... ports) throws Exception {
    // Connect directly to the IRC server.
    //this.server=server;
    boolean connected=false;
    for(int port:ports){
        textBox.append("Trying to connect to "+server+" on port "+port+"...\n");
        try{
            socket = new Socket();
            socket.setSoTimeout(1000);//if nothing happens in 1000 milliseconds, it is gonna advance in the code. IMPORTANT!
            socket.connect(new InetSocketAddress(server, port), 2000);//2000 is the timeout value in milliseconds for the connection
            textBox.append("Connected to "+server+":"+port+"\n");
            connected=true;
            this.port=port;
            break;
        }
        catch(SocketTimeoutException e1){
            textBox.append("Connection timed out\n");
        }
    }
    if(connected){
        writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream( ), "UTF-8"));//utf-8 (international characters)
        reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream( ), "UTF-8"));
        handler.setWriter(writer);
        return 1;//connection successful
    }else{
        textBox.append("Connection timed out, cannot connect to the IRC server\nApp will shut down now.\n");
        return -1;//this means that the connection failed
    }

}//connect

private int logon() throws Exception {//logs the bot in the irc
    writer.write("PASS " + password + "\r\n");//twitch specific stuff
    writer.write("NICK " + nick + "\r\n");
    writer.flush( );

    // Read lines from the server until it tells us we have connected.
    String line = null;
    while ((line = reader.readLine()) != null) {
            rawBox.append(line+"\n");
            if (line.contains("004")) {
                // We are now logged in.
                textBox.append("The bot is successfully logged in\n------------------\n");
                return 1;
            }
            else if (line.contains("Login unsuccessful")) {
                textBox.append("Login was unsuccessful.\n");
                return -1;
            }
    }
    return -1;
}//logon

private void join(String channel) throws Exception {
    // Join the channel. Only for initial use.
    writer.write("JOIN " + channel + "\r\n");
    writer.flush( );

    writer.write("TWITCHCLIENT 3"+"\r\n");
    writer.flush();
}//join

private void mainMethod() throws Exception{
    // Keep reading lines from the server.
    //-------MAIN PROCESS------
    msgInput.setEditable(true);//the textbox is ready to be used

    while (true){
        try{
            line = reader.readLine( );//waits for a line for 1000 millisecs
            handler.setLine(line);
        }catch(SocketTimeoutException e7){
            //if there is no incoming line in a second, it's gonna create an exception
            //we want nothing to do with the exception though
        }

        if(line!=null){
            handler.set_msg_type(0);//default for each line

            if (line.startsWith("PING ")) {
                // We must respond to PINGs to avoid being disconnected.
                handler.sendPONG();
            }

            //Print the raw line received by the bot to the rawBox
            rawBox.append(line+"\n");


            //analyze the line and gather information
            handler.msgAnalyzer();

            //message handling:
            handler.msgHandler();

            //update channellist and other lists
            updateLists();
        }//if

        //other tasks
        handler.otherTasks();
        line=null;


        //send the message 
        if(sendBotMsg){
            handler.sendPRIVMSG(channelToSend, msgToSend);
            sendBotMsg=false;
        }

    }//while

}//mainMethod


//Methods called from the gui are here, but they are irrelevant now

I have tried adding SwingWorker so the initiate stuff runs in the background, but it still freezes the gui.

The program works as intended when I ...

  1. Do not call the initiate method. I still create the object in the actionPerformed method, and I get a functioning GUI.

  2. Do not call the initiate function from the actionPerformed.

For example, when I do this, the bot works as intended:

public static void main(String[] args) throws Exception {
    String server="irc.twitch.tv";
    int[] ports={80,6667};
    String nick ="test";
    String password = "test";
    String master_channel = "#master";
    String master_admin="master";

    //bot object:
    bot irc_bot=new bot(nick, password, master_channel, master_admin);//this is gonna be our irc_bot object
    //initiate  the bot:
    irc_bot.initiate(server, ports);
}

I suspect the thread in which the initiate() runs is blocking the GUI thread somehow. The thing that I do not understand is why it only happens when I call said method from the action listener/window listener/any listener. Any ideas as to how I can fix this?

The button click anonymous class's actionPerformed() method is executed on the Swing thread, so while the code in that block is executed, the GUI cannot do anything else. You need to execute the initiate() method in some other thread.

To prove to yourself that this is the case, use this (terrible) code:

new Thread(){
    public void run() {
        //initiate  the bot:
        try {
            irc_bot.initiate(server, ports);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }}.start();

That should achieve what you are looking for, albeit with terrible code. Then you need to work out how you are going to create and manage that thread. You'll need a way to signal, from your GUI, to the background thread that you want it to stop, probably by interrupting it.

The reason that you do not get this issue when executing the code from the main() method is that you are getting a new Thread for free . When you start your app with main(), you call the constructor on bot and this spawns the UI. Your main method, in its main thread then start executing the bot initiate() method and gets into that loop, whilst the Swing thread is responsible for running the UI.

For button clicks, actionPerformed, is called on the event dispatching thread, and one should return from actionPerformed as fast as possible. Use the invokeLater to do long work a bit later. Otherwise the event queue is blocked (single thread), and the GUI is not responsive.

public void actionPerformed(ActionEvent event){
    SwingUtilites.invokeLater(new Runnable() {
        @Override
        public void run() {
            ... the work
        }
    });
}

    EventQueue.invokeLater(() -> {
        ... the work
    });

The second alternative uses the alternative class for invokeLater, and shortens the code using java 8's lambdas.

I have tried adding SwingWorker so the initiate stuff runs in the background, but it still freezes the gui.

You must call execute() on SwingWorker, not run() method (common mistake).

Like this:

new SwingWorker<Void, Void> {
    @Override
    public Void doInBackground() {
        irc_bot.initiate(server, ports);
        return null;
    }
}.execute();

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