问题描述
我想通过在JLabel上绘制一个BufferedImage来显示图像。
x / yOffset用于在JLabel的中间位置绘制较小的图像。
如果我在IDE中运行代码,它可以正常工作并在JFrame上显示图像。
如果我现在将类构建到jar文件中,它将无法正常工作。
我尝试将Image设置为JLabel的图标,而不使用BufferedImage,但这不是我想要做的。
这是我的图像类的代码:
public class ImageHQ extends JLabel {
BufferedImage img;
int xOffset=0;
int yOffset=0;
public ImageHQ(String path,int xOffset,int yOffset) {
try {
try {
img = ImageIO.read(new File(getClass().getResource(path).toURI()));
} catch (URISyntaxException ex) {
Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE,null,ex);
errorMsg(ex.getMessage());
}
} catch (IOException ex) {
Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE,ex);
errorMsg(ex.getMessage());
}
this.xOffset = xOffset;
this.yOffset = yOffset;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(img,0+xOffset,0+yOffset,null);
repaint();
}
public void errorMsg(String msg) {
JOptionPane.showMessageDialog(null,msg,"Fehler",JOptionPane.ERROR_MESSAGE);
}
}
解决方法
问题出在这里:
new File(getClass().getResource(path).toURI())
不不能保证应用程序资源是单独的文件。 .jar条目只是压缩存档的一部分。它不是硬盘上的单独文件。这就是为什么您不能使用File来读取它的原因。
读取资源的正确方法是完全不尝试将其转换为文件。 getResource
返回一个URL;您只需将该URL直接传递到the ImageIO.read method that takes a URL:
img = ImageIO.read(ImageHQ.class.getResource(path));
请注意使用类文字ImageHQ.class
代替getClass()。这样可以保证您的资源是相对于您自己的类而读取的,而不是相对于可能位于不同包或不同module中的子类。
从InputStream读取
通常来说,有时URL不够用。您也可以使用getResourceAsStream获取从资源读取的开放InputStream。就您而言,您可以这样做:
try (InputStream stream = ImageHQ.class.getResource(path)) {
img = ImageIO.read(stream);
}
但这不是次优的,因为URL可以提供InputStream无法提供的信息,例如文件名,内容类型和图像数据长度的高级知识。
资源路径
您传递给getResource
和getResourceAsStream
的String参数实际上不是文件名。这是相对网址的路径部分。这意味着以C:\
开头的参数将始终失败。
由于参数是URL,因此在所有平台上,它始终使用正斜杠(/
)分隔路径组件。通常,它针对调用getResource *方法的类对象的包进行解析;因此,如果ImageHQ
位于com.example
包中,则此代码:
ImageHQ.class.getResource("logo.png")
将在.jar文件中查找com / example / logo.png。
您可以选择以反斜杠开头的String参数,这将强制其相对于.jar文件的根目录。以上内容可以写为:
ImageHQ.class.getResource("/com/example/logo.png")
ClassLoader中也有getResource *方法,但不应使用这些方法。始终改用Class.getResource或Class.getResourceAsStream。 ClassLoader方法在Java 8和更早的版本中在功能上相似,但是从Java 9开始,Class.getResource在模块化程序中更安全,因为它不会与模块封装冲突。 (ClassLoader.getResource不允许在其String参数的开头使用/
,并始终假定该参数相对于.jar文件的根。)
空返回值
如果path参数没有命名.jar文件中实际存在的资源(或者如果该资源位于不允许读取的模块中),则所有getResource *方法都将返回null
。 。 NullPointerException或IllegalArgumentException是这种情况的常见症状。例如,如果没有logo.png
与.jar文件中的ImageHQ类位于同一软件包中,则getResource将返回null,并将该null传递给ImageIO.read
将导致IllegalArgumentException,如ImageIO.read文档。
如果发生这种情况,您可以通过列出.jar文件的内容来对其进行故障排除。有很多方法可以做到这一点:
- 每个IDE的文件浏览器或文件树都可以检查.jar文件的内容。
- 如果您的JDK位于外壳程序的路径中,则只需执行
jar tf /path/to/myapplication.jar
。 - 在Unix和Linux中,
unzip -v /path/to/myapplication.jar
也将起作用,因为.jar文件实际上是具有一些Java特定条目的zip文件。 - 在Windows中,您可以制作.jar文件的副本,将副本的扩展名更改为.zip,然后使用任何zip工具(包括Windows文件浏览器)将其打开。
回到该示例,如果您的类在com.example
包中,并且您的代码在执行ImageHQ.class.getResource("logo.png")
,则应检查.jar文件的内容以查找com/example/logo.png
。如果不存在,则getResource方法将返回null。
关于打印错误消息
将ex.getMessage()
替换为ex.toString()
。通常,例外消息本身是毫无意义的。您还应该将ex.printStackTrace();
添加到每个catch
块中(或添加一个记录堆栈跟踪的日志记录语句),这样您才能确切知道问题出在哪里。
关于绘画
从不从paintComponent方法调用repaint()
。这会创建一个无限循环,因为repaint()
将迫使Swing绘画系统再次调用paintComponent
。