【问题标题】:How to Update a JLabel Displaying an Image in a While Loop?如何更新在 While 循环中显示图像的 JLabel?
【发布时间】:2018-11-07 17:50:22
【问题描述】:

所以,我正在尝试创建一个非常简单的远程查看器,现在它基本上由一个客户端组成,该客户端截取屏幕截图,将其发送到服务器,服务器将其转换为图像并将其保存为 @987654321 @,然后显示它,然后每三秒重复一次。

一切正常,直到我们到达显示部分。当我最初加载程序时,如果我将图像框设置为显示image.jpg,它就可以工作(只要image.jpg 已经存在——也就是程序运行了不止一次)。但是,当我尝试通过我的Runnable 设置/更新它时,它不起作用。

注意: 项在您将它们放入 Runnables 时会更新,所以这显然不是问题。

这是服务器代码(接收图像的字节数组):

Thread thread = new Thread(new Runnable() {
    public void run() {
        while (true) {
            try {
                // Get client and receive byte array
                ServerSocket server = new ServerSocket(2282);
                Socket socket = server.accept();
                DataInputStream inputData = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
                // Read bytes to byte array
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte buffer[] = new byte[1024];
                for (int s; (s = inputData.read(buffer)) != -1;) {
                  baos.write(buffer, 0, s);
                }
                byte result[] = baos.toByteArray();
                // Create bufferedImage from byte array
                BufferedImage bufferedImage = null;
                ByteArrayInputStream bais = new ByteArrayInputStream(result);
                bufferedImage = ImageIO.read(bais);
                // Save image
                File outputfile = new File("image.jpg");
                ImageIO.write(bufferedImage, "jpg", outputfile);
                // Update image box
                BufferedImage imageIcon = ImageIO.read(new File("image.jpg"));
                BufferedImage resizedImage = resize(imageIcon, 440, 820); // Resizes image with parameters of (BufferedImage img, int height, int width)
                imageBox = new JLabel(new ImageIcon(resizedImage));
                imageBox.setBounds(10, 11, 826, 446);
                frame.getContentPane().add(imageBox);
                // Close everything
                server.close();
                socket.close();
                inputData.close();
                baos.close();
                bais.close();
            }
            catch (IOException e) {
                System.out.println(e);
            }
        }
    }
});
thread.start();

虽然我非常怀疑客户有什么问题,但如果您需要,我也提供了;

while (true) {
    System.out.println("started");
    // Capture the image
    Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
    BufferedImage capture = robot.createScreenCapture(screenRect);

    // Convert the image to a byte array
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(capture, "jpg", baos);
    byte[] bytes = baos.toByteArray();

    // Send byte array over socket connection
    Socket socket = new Socket("CamdensProgrammingDesktop", 2282);
    DataOutputStream outputData = new DataOutputStream(socket.getOutputStream());
    outputData.write(bytes);
    socket.close();
    outputData.close();

    TimeUnit.SECONDS.sleep(3);
}

因此,如果您运行该代码,您应该每三秒就会创建一个 image.jpg,但为什么图像框不会更新?

【问题讨论】:

  • 答案进一步编辑,现在带有工作代码示例。再次,请询问是否有任何不清楚的地方。

标签: java arrays swing sockets bufferedimage


【解决方案1】:

上面的代码有几个问题:

  • 您正在从后台线程中创建 Swing 组件并更新主 GUI(一个 JFrame)——不安全。使用 SwingWorker 帮助您将需要在后台的代码与需要在 Swing 事件线程(EDT)上调用的代码分开。请参阅Lesson: Concurrency in Swing,详细了解为什么这是一个重要问题以及 SwingWorker 如何帮助您解决它。
  • 您正在向容器中添加组件,而不是在容器上调用 revalidate 或 repaint
  • 您在 Swing 组件上调用 setBounds(...),这表明您在 GUI 中的某处使用了空布局,而您绝对不想这样做,因为这会使调试、更新和增强 GUI 非常困难,并有可能使您的程序无法在其他平台上很好地显示。
  • 您正在不必要地创建一个新的 JLabel - 保留一个显示 JLabel,并将其放入 GUI。然后在创建图像后,只需交换标签的图标。执行此操作时无需重新绘制或重新验证。

我自己,我会创建一个 SwingWorker<Void, Icon>,让它从 doInBackground() 方法中生成图像图标,然后使用工作人员发布/处理方法对将图标泵送到 GUI。

例如:

SwingWorker<Void, Icon> worker = new SwingWorker<Void, Icon>() {
    // this is called in background thread
    @Override
    public Void doInBackground() throws Exception {
        boolean connectionStillGood = true;
        while (connectionStillGood) {
            //....
            // code to get image data from socket goes here
            // ....
            // update connectionStillGood value

            BufferedImage bufferedImage = ImageIO.read(....);
            Icon icon = new ImageIcon(bufferedImage);
            publish(icon);  // send icon to process method
        }
        return null;
    }
    
    @Override
    protected void process(List<Icon> chunks) {
        for (Icon icon : chunks) {
            imageBox.setIcon(icon);
        }
    }

};
worker.execute();

一个更具体的例子(一定要先启动 ImageReceiver 类):


ImageReceiver.java

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.*;

public class ImageReceiver extends JPanel {
    private static final long serialVersionUID = 1L;
    public static final int PORT_ADDR = 4040;
    private static final int PREF_W = 400;
    private static final int PREF_H = PREF_W;
    private JLabel imageBox = new JLabel();

    public ImageReceiver() {
        MyWorker worker = new MyWorker();
        worker.addPropertyChangeListener(new WorkerListener());
        worker.execute();
        
        setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(imageBox);
        scrollPane.getViewport().setPreferredSize(new Dimension(PREF_W, PREF_H));
        add(scrollPane);
    }

    private class WorkerListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                MyWorker worker = (MyWorker) evt.getSource();
                try {
                    worker.get();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class MyWorker extends SwingWorker<Void, Icon> {
        // this is called in background thread
        @Override
        public Void doInBackground() throws Exception {
            try (ServerSocket server = new ServerSocket(PORT_ADDR);
                    Socket socket = server.accept();
                    InputStream is = socket.getInputStream();) {

                while (socket != null && !socket.isClosed()) {
                    BufferedImage img = ImageIO.read(is);
                    if (img != null) {
                        publish(new ImageIcon(img));
                    }
                }
            } catch (IOException e) {
                throw e;
            }
            return null;
        }

        @Override
        protected void process(List<Icon> chunks) {
            for (Icon icon : chunks) {
                imageBox.setIcon(icon);
            }
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Receiver");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new ImageReceiver());
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        });
    }
}

ImageProducer.java

import java.awt.image.BufferedImage;
import java.io.*;
import java.net.*;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;

public class ImageProducer {
    public static final String BASE = "https://upload.wikimedia.org/wikipedia/commons/";
    public static final String[] IMG_PATHS = { 
            "7/74/VELEZ_SARSFIELD.png",
            "c/c4/Princeton_Univ_pub-mark.png", 
            "9/92/Union_escudo_antiguo.png", 
            "8/88/Faelog.jpg",
            "8/84/Club-social-y-deportivo-mu%C3%B1iz.png", 
            "0/08/6931st_CSC.jpg",
            "f/ff/Coat_of_arms_of_Mielec.png", 
            "6/64/Francocanadiense.jpg",
            "5/58/Kitty_cat_council.jpg", 
            "thumb/1/12/Cear2002.jpg/400px-Cear2002.jpg",
            "9/94/Wappen_von_Arkantos.png", 
            "3/39/Escudo_chaca_Taq.png", 
            "a/a2/USBCcrest.jpg",
            "0/07/351st_Avn_Co_pocket_patch_1.jpg", 
            "0/06/Steinbach_Lebach_wappen.png",
            "f/f9/90th_Avn_Co_MedHelpatch.jpg" };
    
    public static void main(String[] args) {
        String imagePath = "";
        try (Socket socket = new Socket("localhost", ImageReceiver.PORT_ADDR);
                OutputStream os = socket.getOutputStream();
                InputStream is = socket.getInputStream()) {
            for (String path : IMG_PATHS) {
                imagePath = BASE + path;
                URL inputUrl = new URL(imagePath);
                BufferedImage img = ImageIO.read(inputUrl);
                ImageIO.write(img, "PNG", os);
                TimeUnit.SECONDS.sleep(4);
            }
            
        } catch (IOException e) {
            System.out.println("Image Path: " + imagePath);
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

【讨论】:

    【解决方案2】:

    事实证明,问题在于我每次应该显示图像时都创建一个新的JLabel,但这是行不通的。我解决这个问题的方法是在程序启动时将JLabel 设置为image.jpg,然后在while 循环中,使用.setIcon() 轻松更改图标。

    【讨论】:

    • 正如我的回答中提到的(第四个要点),但你仍然有危险的代码,我支持我的所有其他建议
    • 例如,请查看编辑以回答,如有困惑,请随时提问
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    • 2023-03-18
    • 2015-09-30
    • 1970-01-01
    • 2012-08-03
    • 1970-01-01
    相关资源
    最近更新 更多