如何正确刷新 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();
}
}
}
由这段代码调用:
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...
现在,通常,在执行图像缩放时,我更喜欢使用分而治之的方法,如 Java:保持 JPanel 背景图像的纵横比,但是对于这个例子,为了简单起见,我只是简单地使用了 AffineTransform
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;
}
}
}
相关文章