从 SwingWorker 线程获取结果后,具有特定 JRadioButton 的 JPanel 不会更新

JPanel with a specific JRadioButton does not update after getting result from SwingWorker thread

我正在申请 Java GUI。为了读取需要作为输入的两种类型的文件,我使用 SwingWorkerclass 在两个不同的后台线程中执行操作。一旦读取了这两种类型的文件,GUI 中就会有很多组件用数据更新,JPanel 中的一个 HeatMap。此 JPanel 包含两个 JRadioButton,这两个 JRadioButton 在切换时应显示从应用程序读取的两个文件中获得的两个不同的热图。当用户加载文件时,我可以看到第一个文件的热图。但是当我加载第二个文件时,当第二个单选按钮处于活动状态时,第二个文件的热图未表示(为空)。对于显示两个文件的子文件的 JTabbedPane 也会发生同样的情况。我用

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

虽然在第二个后台线程中更新了JPanel,但这什么都不做。我还为 JPanel 尝试了 getter 和 setter 方法,但这并没有改变任何事情。有什么建议吗?

这是我的第二个 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();
        }
    }
}

最小示例

这个最小的示例甚至根本不显示 imagePanel,而应该显示 JLabel(原始代码中的 HeatMap)。但是,这个例子的整体布局与我的主脚本相似。 JLabelSwingWorker class 本身中生成,然后添加到 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);
    }
}

您的标签或 JPanel 可能会显示,但您看不到它,因为 GUI 不会调整大小以适应它。我建议您放置一张虚拟热图,启动时将一张空白的热图放入 CardLayout 中,该图的大小适合添加的地图,然后让 CardLayout 交换它们。

请注意,如果您所做的只是交换图像,那么我什至不会使用 CardLayout,而是使用单个 JLabel,然后只需交换它通过 setIcon(...).[=15= 显示的 ImageIcons ]

其他问题:

  • 不要设置事物的大小,最好让组件自行调整大小。
  • 不需要在 imagePanel JPanel 上调用 setVisible(true),因为默认情况下 JPanel 是可见的。
  • 如果您将 SwingWorker 绑定到 return 感兴趣的对象而不是 null 或 Void 会更好。这样你就可以在 worker 完成后调用 get() ,从而捕获你可能会丢失的异常。
  • 我自己,我更愿意避免在我的 SwingWorker 中覆盖 done(),而是使用监听 SwingWorker.StateValue.DONE 的 PropertyChangeWorker 来装备我的 SwingWorker。这使我可以避免在我的 SwingWorker 中使用 GUI 代码,从而使其更易于重用。

例如

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;
    }
}

请注意,SwingWorker 完全与 GUI 无关。