Search code examples
javamultithreadingswingswingworker

Update Swing Component using Swing Worker Thread


Currently i'm trying to build Swing application using SwingWorker to continuously update a panel.

What i'm trying to accomplish here is load data from database and for each data will be published as an icon in the panel. the color of the icon is based on the value of severity field. For example:

Example Data In this picture, i have 12 data for object_id A001. the function in the doInBackground() method will loop for 12 times and call publish() function to display the icon in the panel. the color of the icon based on the highest value of the severity field for each object_id.

in the doInBackground() method, i used two method with different result:

  1. Without Thread.sleep() function Console Result UI Result

  2. With Thread.sleep() function 10 milliseconds Console Result UI Result

  3. With Thread.sleep() function 20 milliseconds Console Result UI Result

Program that not use the Thread.sleep() function will print out only the last icon in the console as if the publish() function execute only once.

Program that use the Thread.sleep() function will print out all the icon in the console but it depends on the value that we use in the Thread.sleep() function. The lower the value, some of the icon might not be printed in the console.

All the icon from the database is completely displayed in the UI panel but the color is inconsistent based on the used of Thread.sleep() function and the time used for the delay.

Personally, I don't want to use Thread.sleep() function due to the delay that might be higher when the data is bigger.

the problem that I encountered may be due to the wrong logic in my program because this is the first time I use the SwingWorker.

I also put my whole code regarding this issue if anyone want to try it hehehe. https://drive.google.com/file/d/1Fs1r72HWEzOMwVY2FF9ujtl3eUd-4oIS/view?usp=sharing

the processing data is done in the doInBackground() method then pass the value to process() method to update the panel.

Here is code of the SwingWorker thread:

package com.akbar.thread;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.TreeMap;

import javax.persistence.TypedQuery;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingWorker;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;

import com.akbar.datastructure.TerminalData;
import com.akbar.sources.RoundImageButton;

public class MonitorThread extends SwingWorker<Void, LinkedHashMap<String, String>> {
    private static final String GREEN = "/com/akbar/resources/green48.png";
    private static final String YELLOW = "/com/akbar/resources/yellow48.png";
    private static final String RED = "/com/akbar/resources/red48.png";
    private static final String BLACK = "/com/akbar/resources/black48.png";
    private static final int SEVERITY_LEVEL = 0;
    
    private boolean print = false;
    
    private int newCount = 0;
    private int updCount = 0;
    private int terminalSev;
    private int terminalCount = 0;
    
    private String lastTerminal;
    private String terminalId;
    private String termDetail;
    private String terminalStatus;
    
    private JPanel terminalPanel;
    private JScrollPane terminalScroll;
    
    private TerminalData terminalData;
    
    private TreeMap<String, JButton> updatedTerminal;
    
    private LinkedHashMap<String, LinkedHashMap<String, TerminalData>> terminalList;
    private LinkedHashMap<String, TerminalData> terminalDetail;
    private LinkedHashMap<String, String> zCommand;
    
    private SessionFactory factory = generateSessionFactory();
    private Session session;
    private Transaction tx;
    private TypedQuery<TerminalData> query;
    
    public MonitorThread(JPanel terminalPanel, JScrollPane terminalScroll) {
        this.terminalPanel = terminalPanel;
        this.terminalScroll = terminalScroll;
        
        updatedTerminal = new TreeMap<String, JButton>();
        terminalDetail = new LinkedHashMap<String, TerminalData>();
        terminalList = new LinkedHashMap<String, LinkedHashMap<String, TerminalData>>();
        zCommand = new LinkedHashMap<String, String>();
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Void doInBackground() throws Exception {
        try {
            session = factory.openSession();
            tx = (Transaction) session.beginTransaction();
            query = session.createQuery("FROM TerminalData", TerminalData.class);
            List<TerminalData> result = query.getResultList();
            
            if (result.size() > 0) {
                for (int i = 0; i < result.size(); i++) {
                    terminalData = (TerminalData)result.get(i);
                    terminalSev = 0;
                    termDetail = terminalData.getObjectDetail();
                    terminalId = terminalData.getObjectId();
                    if (terminalList.get(terminalId) != null) {
                        terminalDetail.put(termDetail, terminalData);
                        terminalList.put(terminalId, (LinkedHashMap<String, TerminalData>)terminalDetail.clone());
                        zCommand.put("UPD", terminalId);
                        publish(zCommand);
//                      if (!(terminalId).equals(lastTerminal)) {
//                          lastTerminal = terminalId;
//                          updCount++;
//                      }
                    } else {
//                      if("125006".equals(terminalId)) {
//                          System.out.println("test");
//                      }
                        terminalDetail = new LinkedHashMap<String, TerminalData>();
                        terminalDetail.put(termDetail, terminalData);
                        terminalList.put(terminalId, (LinkedHashMap<String, TerminalData>)terminalDetail.clone());
                        zCommand.put("NEW", terminalId);
                        publish(zCommand);
//                      newCount++;
                    }
                    Thread.sleep(20);
                }
//              System.out.println(String.format("New count: [%s], Update count: [%s]", newCount, updCount));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
        }
        return null;
    }
    
    @Override
    protected void process(List<LinkedHashMap<String, String>> chunks) {
        LinkedHashMap<String, String> test = chunks.get(chunks.size() - 1);
        List<String> commandKey = new ArrayList<String>(test.keySet());
        LinkedHashMap<String, TerminalData> tempDetail;
        List<String> terminalKey;
        List<String> detailKeys;

        TerminalData tempData = new TerminalData();
        
        int sev = 0;
        
        String color;
        String command = commandKey.get(0);
        String terminal = test.get(command);
        zCommand.remove(command);

//      if (!(terminal.equals(lastTerminal))) {
//          lastTerminal = terminal;
//          System.out.println(String.format("Terminal [%s], count [%s]", terminal, ++terminalCount));
//      }
        
        switch (command) {
        case "NEW":
            System.out.println(String.format("Newly Terminal [%s], count [%s]", terminal, ++terminalCount));
            lastTerminal = terminal;
            updatedTerminal = new TreeMap<String, JButton>();
            terminalKey = new ArrayList<String>(terminalList.keySet());
            tempDetail = new LinkedHashMap<String, TerminalData>();
            for (int i = 0; i < terminalKey.size(); i++) {
                tempDetail = terminalList.get(terminalKey.get(i));
                detailKeys = new ArrayList<String>(tempDetail.keySet());
                sev = 0;
                for (int j = 0; j < detailKeys.size(); j++) {
                    tempData = tempDetail.get(detailKeys.get(j));
                    int tempSev = Integer.parseInt(tempData.getSeverity());
                    if (tempSev > sev) {
                        sev = tempSev;
                    }
                }
                color = terminalKey.get(i).equals(terminal) ? BLACK : getIconColor(sev);
                updatedTerminal.put(tempData.getObjectId(), new RoundImageButton(tempData.getObjectId(), color, tempData.getObjectId(), new Dimension(130, 48)));
            }
            updatePanel(updatedTerminal);
            break;
            
        case "UPD":
//          System.out.println(String.format("Updated Terminal [%s], count [%s]", terminal, terminalCount++));
//          if (!(terminal.equals(lastTerminal))) {
//              lastTerminal = terminal;
//              System.out.println(String.format("Terminal [%s], count [%s]", terminal, terminalCount++));
//          }
            sev = 0;
            tempDetail = new LinkedHashMap<String, TerminalData>();
            Component[] comps = terminalPanel.getComponents();
            if (comps.length > 0) {
                for (Component comp : comps) {
                    if (comp instanceof JButton) {
                        if (terminal.equals(comp.getName())) {
                            tempDetail = new LinkedHashMap<String, TerminalData>();
                            tempDetail = terminalList.get(terminal);
                            detailKeys = new ArrayList<String>(tempDetail.keySet());
                            for (int j = 0; j < detailKeys.size(); j++) {
                                tempData = tempDetail.get(detailKeys.get(j));
                                int tempSev = Integer.parseInt(tempData.getSeverity());
                                if (tempSev > sev) {
                                    sev = tempSev;
                                }
//                              if ("125006".equals(terminal)) {
//                                  System.out.println(String.format("Terminal [%s], object detail [%s], severity [%s]", terminal, tempData.getObjectDetail(), sev));
//                              }
                            }
//                          System.out.println(String.format("Terminal [%s], object detail [%s], severity [%s]", terminal, tempData.getObjectDetail(), sev));
                            color = getIconColor(sev);
                            ((AbstractButton) comp).setIcon(new ImageIcon(getClass().getResource(color)));
                            break;
                        }
                    }
                }
            }
            break;
            
        case "RMV":
            break;
        
        default:
            break;
        }
    }
    
    @Override
    protected void done() {
        super.done();
    }
    
    private void updateComponent(String terminal) {
        LinkedHashMap<String, TerminalData> temp = terminalList.get(terminal);
        List<String> key = new ArrayList<String>(temp.keySet());
        TerminalData data;
        int highestSeverity = 0;
        int severity = 0;
        for (int i = 0; i < key.size(); i++) {
            data = temp.get(key.get(i));
            severity = Integer.parseInt(data.getSeverity());
            if (severity > highestSeverity) {
                highestSeverity = severity;
            }
        }
        
        if (highestSeverity > SEVERITY_LEVEL) {
            
        }
    }
    
    private String getIconColor(int severity) {
        if (severity >= 0 && severity <= 10) {
            return GREEN;
        } else if (severity > 10 && severity <= 30) {
            return YELLOW;
        } else if (severity > 30 && severity <= 40) {
            return RED;
        }
        return BLACK;
    }
    
    private TreeMap<String, JButton> retrieveDisplayedTerminal() {
        TreeMap<String, JButton> temp = new TreeMap<String, JButton>();
        Component[] comps = terminalPanel.getComponents();
        if (comps.length > 0) {
            for (Component comp : comps) {
                if (comp instanceof JButton) {
                    temp.put(comp.getName(), (JButton) comp);
                }
            }
        }
        return temp;
    }
    
    private boolean checkCurrentTerminal(String terminal) {
        Component[] comps = terminalPanel.getComponents();
        if (comps.length > 0) {
            for (Component comp : comps) {
                if (comp instanceof JButton) {
                    if ((comp.getName()).equals(terminal)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    private void updatePanel(TreeMap<String, JButton> terminals) {
        final int MAX_COLUMN = 14;
        JButton button;
        Component[] components = terminalPanel.getComponents();
        if (components.length > 0) {
            for (Component comp : components) {
                if (comp instanceof JButton) {
                    terminalPanel.remove(comp);
                    terminalPanel.validate();
                    terminalPanel.repaint();
                    terminalScroll.validate();
                    terminalScroll.repaint();
                }
            }
        }
        
        GridBagConstraints gc = new GridBagConstraints();
        gc.insets = new Insets(0, 5, 5, 0);
        
        int currentLine = 1;
        int size = terminals.size();
        int totalLine = size / MAX_COLUMN;
        if (totalLine == 0) {
            totalLine += 1;
        } else {
            int temp = size % MAX_COLUMN;
            if (temp > 0) {
                totalLine += 1;
            }
        }
        
        int xCount = -1;
        int yCount = 0;
        
        List<String> keyList = new ArrayList<String>(terminals.keySet());
        for (int i = 0; i < size; i++) {
            if (terminals.get(keyList.get(i)) instanceof JButton) {
                button = terminals.get(keyList.get(i));
                if (xCount == MAX_COLUMN - 1) {
                    currentLine++;
                    yCount++;
                    xCount = 0;
                    gc.gridx = xCount;
                    gc.gridy = yCount;
                    gc.weightx = 0.0;
                    gc.weighty = (currentLine == totalLine ? 50.0 : 0.1);
                    gc.anchor = GridBagConstraints.NORTHWEST;
                    gc.fill = GridBagConstraints.HORIZONTAL;
                } else {
                    xCount++;
                    gc.gridx = xCount;
                    gc.gridy = yCount;
                    gc.weightx = 0.0;
                    gc.weighty = 0.1;
                    gc.anchor = GridBagConstraints.NORTH;
                    gc.fill = GridBagConstraints.NONE;
                }
                terminalPanel.add(button, gc);
            }
        }
        terminalPanel.validate();
        terminalPanel.repaint();
        terminalScroll.validate();
        terminalScroll.repaint();
    }
    
    private SessionFactory generateSessionFactory() {
        try {
            return new Configuration().configure().buildSessionFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

thanks in advance.


Solution

  • When the "publish" method if invoked very frequently the values will probably be accumulated before the "process" is invoked on the EDT.

    That is why the "process" method receives a List of object to publish. It is the responsibility of your code to iterate through the List and update your GUI using all the data in the List.

    So given that your "doInBackground" logic uses a for loop, I would suggest multiple values are accumulated and your "process" method is only process one of many.

    When you use a Thread.sleep(...) you limit the number of objects that will potentially be consolidated into a single "process" event.

    So the solution is to make sure your "process" method iterates through all the objects in the List.