如何正确刷新JFrame中的图像?
这是一个困扰我几个小时的问题,我自己找不到解决方案...
This is a problem that disturbs me for few hours now and I'm not able to find a solution by myself...
我在网络上发现了类似的话题,但是我无法很好地解释解释并找到尽可能简单的解决方案来解决同样的问题.我也看过 EDT 和 SwingWorker API文档,但是对我来说太复杂了:(
I've found similar topics all around the net, but I couldn't find exact same problem with well explained and as simple as possible solution. I've also looked at EDT and SwingWorker API docs, but it was far too complicated for me :(
所以,让我们开始讨论.我有一个带有JLabel的简单JFrame,其中包含我的图片:
So, let's get to the point. I have a simple JFrame with JLabel inside, that consist of my image:
private static class MyJLabel extends JLabel {
private ImageIcon img = null;
public MyJLabel(ImageIcon img) {
super();
this.img = img;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);
}
}
private static class MyJFrame extends JFrame implements Runnable {
private BufferedImage img = null;
private MyJLabel label = null;
public MyJFrame(BufferedImage image, String title) {
super(title);
img = image;
}
@Override
public void run() {
Dimension dims = new Dimension(img.getWidth(), img.getHeight());
dims = new Dimension(dims.width / 2, dims.height / 2);
label = new MyJLabel(new ImageIcon(img));
label.setPreferredSize(dims);
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
label.repaint();
}
});
setLayout(new BorderLayout());
getContentPane().add(BorderLayout.CENTER, label);
setLocation(200, 200);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
pack();
setVisible(true);
}
public void changeImage(BufferedImage image) {
img = image;
if (label != null) {
label.setIcon(new ImageIcon(img));
label.repaint();
}
}
}
它是由这段代码调用的:
It's invoked by this piece of code:
buffer = receiveImage(in); // download image
MyJFrame f = null;
javax.swing.SwingUtilities.invokeLater(f = new MyJFrame(buffer, "RDP"));
int x = 0;
while (x <= 15) {
txt.println("next"); // notify server that we are ready
while (true) { // wait for server
if (reader.readLine().equals("ready")) break;
}
buffer = receiveImage(in); // download image
// do some magic here and refresh image somehow :(
f.changeImage(buffer); // does not work!
x++;
}
不幸的是,我使用 changeImage 方法的方法不起作用-什么也没发生(GUI启动但从未更新).
Unfortunately, my approach with changeImage method does not work - nothing happens (GUI starts but never gets updated).
在此方面,我将不提供任何帮助. 简单,带有正确解释的工作示例,将是最受赞赏的;)
I'd appreciate little help with this. Simple, working example with proper explanation would be appreciated the most ;)
问候!
我个人而言,可以在将其应用于标签之前调整其大小,也可以使用JPanel
进行绘画. JLabel
具有很多功能.
Personally, I would either resize it before applying it to the label or use a JPanel
to perform the painting. JLabel
has to much functionality dragging around with it.
举例来说,您遇到的问题是您实际上是在使用setIcon
设置图像,但是使用paintComponent
在其顶部绘制另一个(初始)图像
Case in point, the problem you're having is you're actually using setIcon
to set the image, but using paintComponent
to paint another (the initial) image over the top of it
您的自定义标签将ImageIcon
作为初始参数并将其绘制为这样...
Your custom label takes a ImageIcon
as a inital parameter and paints it as such...
private static class MyJLabel extends JLabel {
private ImageIcon img = null;
public MyJLabel(ImageIcon img) {
super();
this.img = img;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);
}
}
您可以这样初始化它...
You initialise it as such...
label = new MyJLabel(new ImageIcon(img));
应注意,如果使用JLabel
的Icon
支持,则此...
It should be noted that if you used the Icon
support of JLabel
, this...
label.setPreferredSize(dims);
无关紧要,因为JLabel
会使用图标大小来确定其首选大小...但是无论如何...
Would be irrelevant as the JLabel
would use the icon size to determine it's preferred size...but any way...
然后使用此图标更新图标.
Then you update the icon using this..
img = image;
if (label != null) {
label.setIcon(new ImageIcon(img));
label.repaint();
}
应该指出的是,根据您的示例,这实际上是在EDT之外被调用的,这很危险,并且可能导致油漆变脏
但是setIcon
永远不会更改MyLabel
中img
的值,因此,当调用paintComponent
方法时,实际上是在更新中提供的图标上绘画...
But setIcon
never changes the value of img
within MyLabel
, so when your paintComponent
method is called, you are actually painting over the icon you have supplied in the update...
// Paint the new Icon
super.paintComponent(g);
// Paint the old/initial image...
g.drawImage(img.getImage(), 0, 0, getWidth(), getHeight(), this);
已更新
个人而言,我要做的是使用JPanel
之类的东西创建一个自定义组件,并根据面板的当前大小缩放原始图像,例如...
Personally, what I would do is create a custom component, using something like a JPanel
and scale the original image based on the current size of the panel, for example...
Now, normally, when performing image scaling I prefer to use a divide and conqure approach as demonstrated in Java: maintaining aspect ratio of JPanel background image, but for this example, I've simply used and AffineTransform
for simplicity sake
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ScalableImageExample {
public static void main(String[] args) {
new ScalableImageExample();
}
public ScalableImageExample() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
ResizableImagePane pane = new ResizableImagePane();
pane.setImage(...);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class ResizableImagePane extends JPanel {
private Image img;
public ResizableImagePane() {
}
public void setImage(Image value) {
if (img != value) {
Image old = img;
this.img = value;
firePropertyChange("image", old, img);
revalidate();
repaint();
}
}
public Image getImage() {
return img;
}
@Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(this), img.getHeight(this));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
double scaleFactor = getScaleFactorToFit(new Dimension(img.getWidth(this), img.getHeight(this)), getSize());
int x = (int)((width - (img.getWidth(this) * scaleFactor)) / 2);
int y = (int)((height - (img.getHeight(this) * scaleFactor)) / 2);
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.scale(scaleFactor, scaleFactor);
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
g2d.dispose();
}
}
public double getScaleFactor(int iMasterSize, int iTargetSize) {
return (double) iTargetSize / (double) iMasterSize;
}
public double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
}
}