Non Sloppy design patterns for updating GUI from a different thread in Java

144 views Asked by At

What design pattern is best for updating the GUI when dealing with other threads in Java (Swing)?

For example, imagine an Object (like a custom JPanel) that has a JList that has a DefaultListModel supporting it. A threading listening on a Socket can receive data and then wants to update the JList from the information that came in on the socket.

I understand the SwingUtilities.invokeLater, but that seems like sloopy code, because in reality I have many different functions that can be called (from non EDT threads) that manipulate different GUI components.

The idea that I thought of is creating some kind of messaging system with an ArrayBlockingQueue. Basically I implement Runnable and in the SwingUtilities.invokeLater method call I pass in this. Then the method gets executed, but it doesn't really know what to do, but that is where I pop the "messages" from the thread safe ArrayBlockingQueue.

Is there a better design pattern than this? My base JPanel Class

public class JPanelGUIThread extends JPanel implements Runnable
{
    protected ArrayBlockingQueue<Object> guiUpdateMessages;
    
    public JPanelGUIThread()
    {
        guiUpdateMessages = new ArrayBlockingQueue<Object>(10);
    }
    
    @Override
    public void run()
    {
        while(guiUpdateMessages.size() > 0)
        {
            try
            {
                Object data = guiUpdateMessages.take();
                
                if(data instanceof Object[])
                {
                    handleGUIUpdateArray((Object[])data);
                }
                else
                {
                    handleGUIUpdateObject(data);
                }
                
            } 
            catch (InterruptedException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
    }
    
    public void handleGUIUpdateArray(Object[] objectArray)
    {
        
    }
    public void handleGUIUpdateObject(Object object)
    {
        
    }
}

My main JPanel



    
    public JLabel getChatLabel()
    {
        return chatLabel;
    }

    public JTextArea getChatArea()
    {
        return chatArea;
    }

    public JScrollPane getChatScrollPane()
    {
        return chatScrollPane;
    }

    public JTextField getMychat()
    {
        return mychat;
    }

    public JButton getSendButton()
    {
        return sendButton;
    }

    //This method is called from the EDT, so no need to perform adding messages
    @Override
    public void actionPerformed(ActionEvent e)
    {
        if(e.getSource() == sendButton)
        {
            client.sendChatInformation(mychat.getText());
            mychat.setText("");
        }
    }

    public void clearOldChat()
    {
        Object[] data = new Object[3];
        data[0] = chatArea;
        data[1] = MessageType.SET;
        data[2] = "";
        guiUpdateMessages.add(data);
        SwingUtilities.invokeLater(this);
    }


    @Override
    public void handleGUIUpdateArray(Object[] objectArray)
    {
        if(objectArray[0] == chatArea)
        {
            if(objectArray[1] == MessageType.APPEND)
            {
                chatArea.append((String) objectArray[2]);
            }
            else if(objectArray[1] == MessageType.SET)
            {
                chatArea.setText((String) objectArray[2]);
            }
            
        }
    }
}
1

There are 1 answers

0
Joni On

You are reinventing the "event queue" that makes a graphical user interface work in the first place. There already is a queue where you can add new messages, implemented in the java.awt.EventQueue class.

The convenient way to add a message to the event queue is with SwingUtilities.invokeLater(Runnable). The Runnable instance that you pass in should contain all the information necessary to process the event. Even better: since it's a Runnable, it can encapsulate the code you need to run to process the event.

For example: here is how you can encapsulate your general "object array" message within a Runnable, and add it to the event queue.

        public void clearOldChat() {
            Object[] data = new Object[3];
            data[0] = chatArea;
            data[1] = MessageType.SET;
            data[2] = "";
            SwingUtilities.invokeLater(new GUIUpdateArrayHandler(data));
        }

        class GUIUpdateArrayHandler implements Runnable {

            Object[] objectArray;

            public GUIUpdateArray(Object[] objectArray) {
                this.objectArray = objectArray;
            }

            public void run() {
                if (objectArray[0] == chatArea) {
                    if (objectArray[1] == MessageType.APPEND) {
                        chatArea.append((String) objectArray[2]);
                    } else if (objectArray[1] == MessageType.SET) {
                        chatArea.setText((String) objectArray[2]);
                    }

                }
            }
        }

Personally, I would create separate "Runnable" classes for each type of message you want to send instead of one generic GUIUpdateArrayHandler: like AppendHandler for MessageType.APPEND, SetHandler for MessageType.SET, but if you think it's "less sloppy" to have them in the same place in a single handler, up to you.