1

I am making a Java GUI application. For reading two types of files required as an input, I am using the SwingWorkerclass which performs the operation in two different background threads. Once, the two types of files are read, there are a lot of components which get updated in the GUI with data and a HeatMap in one of the JPanel. This JPanel contains two JRadioButton, which on switching should display the two different heat maps obtained from the two files read by the application. I can see the heatmap of the first file when the user loads the file. But when I load the second file, heatmap for the second file in not represented (is empty) when the second radio button is active. Same happens, for the JTabbedPane which display the sub files from the two files. I use

imagePanel.removeAll();
imagePanel.add(preprocessedIntensityMap, BorderLayout.CENTER); 
panel.repaint();
panel.revalidate();

while in the second background thread to update the JPanel, but this does nothing. I also tried getter and setter method for the JPanel but this did not change things. Any suggestions?

Here is the compete code of my second SwingWorker class:

public class PeaklistReadWorker extends SwingWorker<REXP, Object> {

    private RConnection rc1;
    private GUIMain guiClassObject;
    private File fileName;
    private JTree tree;
    private String currentPath;
    private REXP preprocessedSpectrumObjects;
    private float minMzValue;
    private float maxMzValue;
    volatile HeatMap preprocessedIntensityMap;
    private JLabel coordinates;
    private JScrollPane spectralFilesScrollPane;
    private final JPanel peakPickedFilesPanel;
    private JTabbedPane tabbedSpectralFiles;
    private final JLabel statusLabel;
    private JMenuItem CGMenuItem;
    private JMenuItem TGMenuItem;
    private JPanel imagePanel;
    private GenerateIntensityMap gim = new GenerateIntensityMap();
    private SortFile sf = new SortFile();
    private JDialog progressDialog;

    public PeaklistReadWorker(GUIMain guiClassObject, File fileName, JTree tree, String currentPath, REXP
            preprocessedSpectrumObjects, float minMzValue, float maxMzValue, HeatMap preprocessedIntensityMap, JLabel coordinates,
                              JScrollPane spectralFilesScrollPane, JPanel peakPickedFilesPanel, JTabbedPane tabbedSpectralFiles,
                              JLabel statusLabel, JMenuItem CGMenuItem, JMenuItem TGMenuItem, JPanel imagePanel, JDialog progressDialog)
    {
        this.guiClassObject = guiClassObject;
        this.fileName = fileName;
        this.tree = tree;
        this.currentPath = currentPath;
        this.preprocessedSpectrumObjects = preprocessedSpectrumObjects;
        this.minMzValue = minMzValue;
        this.maxMzValue = maxMzValue;
        this.preprocessedIntensityMap = preprocessedIntensityMap;
        this.coordinates = coordinates;
        this.spectralFilesScrollPane = spectralFilesScrollPane;
        this.peakPickedFilesPanel = peakPickedFilesPanel;
        this.tabbedSpectralFiles = tabbedSpectralFiles;
        this.statusLabel = statusLabel;
        this.CGMenuItem = CGMenuItem;
        this.TGMenuItem = TGMenuItem;
        this.imagePanel = imagePanel;
        this.progressDialog = progressDialog;
    }

    @Override
    protected REXP doInBackground() throws Exception {

        try {
            rc1 = new RConnection();
            rc1.assign("importPreprocessedSpectra", currentPath.concat("/importPreprocessedSpectra.R"));
            rc1.eval("source(importPreprocessedSpectra)");
            rc1.assign("inputFileDirectory", fileName.toString());
            preprocessedSpectrumObjects = rc1.eval("importPreprocessedSpectra(inputFileDirectory)");

            preprocessedIntensityMap = gim.generateIntensityMap(preprocessedSpectrumObjects, currentPath, minMzValue, maxMzValue, Gradient.GRADIENT_Rainbow, "PROCESSED");           
        } catch (RserveException e1) {
            e1.printStackTrace();
        } catch (REXPMismatchException e1) {
            e1.printStackTrace();
        }   
        return preprocessedSpectrumObjects;
    }

    /**
     * Process method to take care of intermediate results - works in EDT
     * @param chunks
     */
    @Override
    protected void process(List<Object> chunks) {
    }

    @Override
    public void done() {

        try {
            REXP preprocessedSpectrumObjects = get();
            guiClassObject.returnFromBackgroundPeaklistObjects(preprocessedSpectrumObjects);

            // list only files with .txt extension from the directory selected
            File[] filesInDirectory = fileName.listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".txt");
                }
            });
            guiClassObject.setPreprocessedIntensityMap(preprocessedIntensityMap);

            // Calls sortByNumber method in class SortFile to list the files number wise
            filesInDirectory = sf.sortByNumber(filesInDirectory);
            tree = new JTree(guiClassObject.addNodes(null, filesInDirectory, fileName));
            guiClassObject.setTree(tree);
            DefaultMutableTreeNode firstLeaf = ((DefaultMutableTreeNode) tree.getModel().getRoot()).getFirstLeaf();
            tree.setSelectionPath(new TreePath(firstLeaf.getPath()));
            guiClassObject.updateSpectralTableandChartPICKED(firstLeaf);

            // Add a tree selection listener
            tree.addTreeSelectionListener(new TreeSelectionListener() {

                public void valueChanged(TreeSelectionEvent e) {
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
                    guiClassObject.updateSpectralTableandChartPICKED(node);
                }
            });

            imagePanel.removeAll();
            imagePanel.add(preprocessedIntensityMap, BorderLayout.CENTER);
            guiClassObject.panelRefresh(imagePanel);
            coordinates.setBounds(31, 31, preprocessedIntensityMap.getWidth() - 31, preprocessedIntensityMap.getHeight() - 31);

            preprocessedIntensityMap.addMouseListener(guiClassObject);
            preprocessedIntensityMap.addMouseMotionListener(guiClassObject);
            spectralFilesScrollPane.setViewportView(tree);
            spectralFilesScrollPane.setPreferredSize(peakPickedFilesPanel.getSize());
            peakPickedFilesPanel.add(spectralFilesScrollPane);
            tabbedSpectralFiles.validate();
            tabbedSpectralFiles.repaint();

            CGMenuItem.setEnabled(true); // active now
            TGMenuItem.setEnabled(true); // active now
            progressDialog.dispose();//close the modal dialog
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Minimal Example

This minimal example does not even display the imagePanel at all, where the JLabel (HeatMap in the original code) should be displayed. But, the overall layout of this example is similar to my main script. The JLabel is generated in the SwingWorker class itself and then is added to the imagePanel.

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;

    public class GuiClass {

        public static void main(String[] args) {
            new GuiClass();
        }
        public GuiClass() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (UnsupportedLookAndFeelException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } {
                    }
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }

        public class TestPane extends JPanel {

            public TestPane() {

                final String raw = "Raw";
                final String preprocessed = "Preprocessed";

                final JRadioButton rawImage = new JRadioButton(raw, true);
                JRadioButton peakPickedImage = new JRadioButton(preprocessed);
                rawImage.setActionCommand(raw);
                peakPickedImage.setActionCommand(preprocessed);

                ButtonGroup radioButtonGroup = new ButtonGroup();
                radioButtonGroup.add(rawImage);
                radioButtonGroup.add(peakPickedImage);

                JPanel buttons = new JPanel(new
                        FlowLayout(FlowLayout.CENTER, 5, 5));
                buttons.add(rawImage);
                buttons.add(peakPickedImage);

                add(buttons, BorderLayout.SOUTH);

                final CardLayout cl = new CardLayout();
                final JPanel imagePanel = new JPanel(cl);
                imagePanel.setSize(100,100);
                add(imagePanel);
                imagePanel.setVisible(true);

                ActionListener al = new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        if (rawImage.isSelected() && rawImage.isEnabled()) {
                            ImageCreaterRaw icr = new ImageCreaterRaw(imagePanel, raw, cl);
                            icr.execute();

                        } else {
                            ImageCreaterPreprocessed icp = new ImageCreaterPreprocessed(imagePanel, preprocessed, cl);
                            icp.execute();
                        }
                    }
                };
                rawImage.addActionListener(al);
                peakPickedImage.addActionListener(al);
            }
        }
    }

    class ImageCreaterRaw extends SwingWorker<Void, Void> {

    private JPanel imagePanel;
   // private HeatMap rawHeatMap;
    private JLabel labelR;
    private String raw;
    private CardLayout cl;

    public ImageCreaterRaw(JPanel imagePanel, String raw, CardLayout cl)
    {
        this.imagePanel = imagePanel;
        this.raw = raw;
        this.cl = cl;

    }

    @Override
    protected Void doInBackground() throws Exception {
        double[][] data = HeatMap.generateSinCosData(200);
        boolean useGraphicsYAxis = true;
      //  System.out.println("I am herr");
      //  rawHeatMap = new HeatMap(data, useGraphicsYAxis, Gradient.GRADIENT_BlackToWhite);
        labelR = new JLabel("Label for Raw");
        try { Thread.currentThread().sleep(3000); }
        catch(InterruptedException e) {}
        return null;
    }

    public void done()
    {
       // imagePanel.add(rawHeatMap, raw);
        imagePanel.add(labelR, raw);
        cl.show(imagePanel, raw);
    }
}

// First SwingWorker Class

class ImageCreaterPreprocessed extends SwingWorker<Void, Void> {

    private JPanel imagePanel;
    private JLabel labelP;
    private String preprocessed;
    private CardLayout cl;

    public ImageCreaterPreprocessed(JPanel imagePanel, String preprocessed, CardLayout cl)
    {
        this.imagePanel = imagePanel;
        this.preprocessed = preprocessed;
        this.cl = cl;
    }

    @Override
    protected Void doInBackground() throws Exception {
        double[][] data = HeatMap.generateSinCosData(200);
       // boolean useGraphicsYAxis = true;
       // preprocessedHeatMap = new HeatMap(data, useGraphicsYAxis, Gradient.GRADIENT_BlueToRed);
        labelP = new JLabel("Label for Preprocessed");
        try { Thread.currentThread().sleep(3000); }
        catch(InterruptedException e) {}
        return null;
    }

    public void done()
    {
        //imagePanel.add(preprocessedHeatMap, preprocessed);
        imagePanel.add(labelP, preprocessed);
        cl.show(imagePanel, preprocessed);
    }
}
novicegeek
  • 773
  • 2
  • 9
  • 29
  • 2
    1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) Please learn common Java nomenclature (naming conventions - e.g. `EachWordUpperCaseClass`, `firstWordLowerCaseMethod()`, `firstWordLowerCaseAttribute` unless it is an `UPPER_CASE_CONSTANT`) and use it consistently. – Andrew Thompson May 14 '16 at 12:04
  • 2
    You're asking, "why isn't selecting my JRadioButton causing my data to be displayed", and then posting a ton of code, most of it completely unrelated to the problem at hand, and making it difficult if not impossible for folks to help. I second @AndrewThompson's recommendation above -- simplify your code and your problem by creating and posting a [mcve] or [sscce](http://sscce.org). – Hovercraft Full Of Eels May 14 '16 at 12:37
  • 2
    @AndrewThompson Yes I am preparing a minimal example. Will update the question as soon as I have finished writing it. – novicegeek May 14 '16 at 12:41
  • 3
    `imagePanel.removeAll(); imagePanel.add(preprocessedIntensityMap, BorderLayout.CENTER); panel.repaint(); panel.revalidate();` BTW - Use a [`CardLayout`](http://download.oracle.com/javase/8/docs/api/java/awt/CardLayout.html) as shown in [this answer](http://stackoverflow.com/a/5786005/418556). This is the sort of thing `CardLayout` is **designed for.** – Andrew Thompson May 14 '16 at 12:57
  • Yes. The example in the link is exactly what I want. I think I should edit my question because, I have a JPanel which contains two JRadioButton and 1 JPanel components in it. This JPanel component should be updated based on changes in the JRadioButton selection. – novicegeek May 14 '16 at 13:08
  • @HovercraftFullOfEels I have added a minimal example – novicegeek May 14 '16 at 15:05
  • 1
    Can't run it as is, so you've more to do, mainly changing from outside class need, to an internal mock representation using dummy data and Thread.sleep to mimic a long-running process. The data produced isn't the issue here regardless, it's the handling of it by the GUI. – Hovercraft Full Of Eels May 14 '16 at 15:13
  • What happens when you re-size your GUI after the 2nd map loads? – Hovercraft Full Of Eels May 14 '16 at 15:30
  • I have edited the minimal example and added Thread.sleep and `JLabel` instead of `HeatMap` – novicegeek May 14 '16 at 15:30

1 Answers1

2

Your label or JPanel is likely showing up, but you're not seeing it because the GUI does not re-size to accommodate it. I suggest that you place a dummy heat map, a blank one into the CardLayout at start up, one that is the appropriate size for the added maps, and then let the CardLayout swap them.

Note if all you're doing is swapping images, then I wouldn't even use a CardLayout but rather a single JLabel, and then simply swap ImageIcons that it displays via setIcon(...).

Other issues:

  • Don't set sizes of things but rather best if you can have components size themselves.
  • No need to call setVisible(true) on the imagePanel JPanel since JPanels are by default visible.
  • Better if you rig your SwingWorker to return the object of interest rather than null or Void. This way you can call get() on the worker when its done, and thereby catch exceptions that you otherwise could be missing.
  • Myself, I much prefer to avoid overriding done() in my SwingWorker and instead rig my SwingWorker with a PropertyChangeWorker listening for SwingWorker.StateValue.DONE. This allows me to avoid having GUI-code within my SwingWorker, making it more re-usable.

e.g.,

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Window;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import java.util.concurrent.ExecutionException;

import javax.swing.*;

@SuppressWarnings("serial")
public class GuiLongRunning extends JPanel {
    public static final int IMG_W = 800;
    public static final int IMG_H = 500;
    BufferedImage blankImage = new BufferedImage(IMG_W, IMG_H, BufferedImage.TYPE_INT_ARGB);
    private Icon blankIcon = new ImageIcon(blankImage);
    private JLabel imageLabel = new JLabel(blankIcon);
    private ButtonGroup buttonGroup = new ButtonGroup();
    private JDialog runningDialog = null;

    public GuiLongRunning() {
        JPanel topPanel = new JPanel();
        for (ImageType imageType : ImageType.values()) {
            JRadioButton radioButton = new JRadioButton(imageType.getName());
            radioButton.addItemListener(new RadioItemListener(imageType));
            topPanel.add(radioButton);
            buttonGroup.add(radioButton);
            int mnemonic = imageType.getName().charAt(0);
            radioButton.setMnemonic(mnemonic);
        }

        setLayout(new BorderLayout());
        add(topPanel, BorderLayout.PAGE_START);
        add(imageLabel, BorderLayout.CENTER);
    }

    public void workerRunning(boolean running) {
        if (!running) {
            if (runningDialog != null) {
                runningDialog.dispose();
            }
        } else {
            if (runningDialog == null) {
                JProgressBar progressBar = new JProgressBar();
                progressBar.setPreferredSize(new Dimension(300, 30));
                progressBar.setIndeterminate(true);
                Window thisWindow = SwingUtilities.getWindowAncestor(GuiLongRunning.this);
                runningDialog = new JDialog(thisWindow, "Creating Image", ModalityType.APPLICATION_MODAL);
                runningDialog.add(progressBar);
                runningDialog.pack();
                runningDialog.setLocationRelativeTo(thisWindow);
            }
            runningDialog.setVisible(true);
        }
    }

    private class RadioItemListener implements ItemListener {
        private ImageType imageType;

        public RadioItemListener(ImageType imageType) {
            this.imageType = imageType;
        }

        @Override
        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                int w = GuiLongRunning.IMG_W;
                int h = GuiLongRunning.IMG_H;
                ProcessImageWorker worker = new ProcessImageWorker(imageType, w, h);
                worker.addPropertyChangeListener(new WorkerListener());
                worker.execute();
                workerRunning(true);
            }
        }
    }

    private class WorkerListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
                ProcessImageWorker worker = (ProcessImageWorker) evt.getSource();
                try {
                    Icon icon = worker.get();
                    imageLabel.setIcon(icon);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
                // delay it a little so won't call this before the dialog is
                // visible
                SwingUtilities.invokeLater(() -> workerRunning(false));
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        GuiLongRunning mainPanel = new GuiLongRunning();
        JFrame frame = new JFrame("GuiLongRunning");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }
}

class ProcessImageWorker extends SwingWorker<Icon, Void> {
    private static final float DELTA_H = 0.01f;
    private static final long SLEEP_TIME = 1500;
    private ImageType imageType;
    private int width;
    private int height;
    private Random random = new Random();

    public ProcessImageWorker(ImageType imageType, int width, int height) {
        this.imageType = imageType;
        this.width = width;
        this.height = height;
    }

    @Override
    protected Icon doInBackground() throws Exception {
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Raster raster = img.getData();
        DataBufferInt dataBuffer = (DataBufferInt) raster.getDataBuffer();
        int[][] data = dataBuffer.getBankData();

        if (imageType == ImageType.RAW) {
            rawProcess(data);
        } else if (imageType == ImageType.PREPROCESSED) {
            colorProcess(data);
        }
        img.setData(raster);
        Thread.sleep(SLEEP_TIME); // !! 

        return new ImageIcon(img);
    }

    private int[][] rawProcess(int[][] data) {
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                int randInt = (int) (256 * Math.random());
                data[i][j] = new Color(randInt, randInt, randInt).getRGB();
            }
        }
        return data;
    }

    // some random colors
    private int[][] colorProcess(int[][] data) {
        float h = 0f;
        for (int i = 0; i < data.length; i++) {
            float h2 = h;
            for (int j = 0; j < data[i].length; j++) {
                h2 += DELTA_H * random.nextFloat();
                h2 += 1f;
                h2 %= 1f;

                Color c = Color.getHSBColor(h2, 1f, 1f);
                int randInt = c.getRGB();
                data[i][j] = randInt;
            }
        }
        return data;
    }
}

enum ImageType {
    RAW("Raw"), PREPROCESSED("Preprocessed");
    private String name;

    private ImageType(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

Note that the SwingWorker is completely GUI agnostic.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I just uploaded an image of my GUI when I launch it. The thing is, when I upload data from the first file, I can see the `HeatMap` in the correct `JPanel`, without any issues related to resizing or anything. But, when I upload data from the second file, the second Preprocessed `JRadioButton` is active, but there is no heatmap, it displays an empty `JPanel`. I would like to add that, this was not the issue when I had not implemented `SwingWorker` threads for reading the two file types. The image swapping worked perfectly. – novicegeek May 14 '16 at 15:42
  • I think, there is some issue with variable being returned from the background thread to the EDT, that what I can think of. – novicegeek May 14 '16 at 15:42
  • @novicegeek: you may need to improve your [mcve] so that it reproduces your problem, since currently it does not. – Hovercraft Full Of Eels May 14 '16 at 15:44
  • I also added a screenshot of my GUI with the two states. Yes, I will change the example to add more details and would try to make the issue reproducible. – novicegeek May 14 '16 at 15:57
  • @novicegeek: that's a pretty cool looking GUI and heat map. And yes, it looks like you're swapping images. So I'd make my SwingWorker a `SwingWorker` or `SwingWorker`, and then return the image or image icon from the worker. – Hovercraft Full Of Eels May 14 '16 at 16:16
  • @novicegeek: I'd then swap icons using myLabel.setIcon – Hovercraft Full Of Eels May 14 '16 at 16:21
  • Thank you for the minimal example. It looks very clean specially the `SwingWorker` class without the `GUI` components in it. I am trying to edit my code like wise. However, I had a question, at the moment, my SwingWorker class already returns an object of class `REXP` which is used by many `GUI` components later. Should I edit the code in a manner that I `publish() and process()` this `REXP` object when it is generated and then return an `Icon` object. Moreover, cannot I return a `HeatMap` object rather, since the image generated from the data is of class `HeatMap`? – novicegeek May 16 '16 at 10:18
  • 1
    @novicegeek: I would use publish/process to return interim data if it is produced by the worker as it runs and if needed by the GUI. The final return type can be whatever is needed. A HeatMap object would be perfectly fine. – Hovercraft Full Of Eels May 16 '16 at 13:19
  • Perfect. Thank you! Still working on editing the code. Will update as soon as it works. – novicegeek May 16 '16 at 13:21
  • Your suggestion to implement a listener and not use GUI components in the `done()` of the Worker class did wonders. I still have the `imagePanel` intact with the HeatMap object displayed within it, and the image switching works perfectly with this :) Just that, I now have to take care of the tab switching which is still showing a blank pane when I switch tabs. I will check whats causing that and will fix it. A big thank you for your example :) – novicegeek May 18 '16 at 17:51
  • @novicegeek: thanks for the update on your problem, and glad my suggestions could help. Yours is an interesting problem and looks to be a very interesting project. Good luck with it! – Hovercraft Full Of Eels May 18 '16 at 20:35