为什么paint()/ paintComponent()从未调用过?

在过去的两天里,我试图了解 Java如何处理图形,但是在这方面却失败了。 我的主要问题是如何以及何时应该调用paint()(或更新的paintComponent())。

在下面的代码中我看到了什么时候创建的东西,paintComponent()从不被调用,除非我自己手动添加一个调用或者调用JFrame.paintAll()/ JFrame.paintComponents()。

我将paint()方法重命名为paintComponent(),希望能解决我永远不会被调用的问题(即使在repaint()),但没有运气。

package jpanelpaint; import java.awt.*; import javax.imageio.*; import javax.swing.*; import java.io.*; import java.util.ArrayList; public class ImageLoadTest extends JComponent { ArrayList list; public ImageLoadTest() { list = new ArrayList(); try { //create the images (a deck of 4 cards) for(String name : createImageFileNames(4)){ System.err.println(name); list.add(ImageIO.read(new File(name))); } } catch (IOException e) { } } protected void paintComponent(Graphics g) { int yOffset=0; System.err.println("ImageLoadTest.paintComponent()"); for(Image img : list) { g.drawImage(img, 0, yOffset, null); yOffset+=20; } } public static void main(String args[]) throws InterruptedException { JFrame frame = new JFrame("Empty JFrame"); frame.setSize(new Dimension(1000, 500)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); Thread.sleep(1000); frame.setTitle("Loading images"); ImageLoadTest ilt = new ImageLoadTest(); frame.add(ilt); //update the screen //DOESN'T WORK. only works if I call frame.paintAll(frame.getGraphics()) ilt.repaint(); frame.repaint(); Thread.sleep(1000); frame.setTitle("Setting background"); ilt.setBackground(Color.BLACK); //update the screen - DOESN'T WORK even if I call paintAll .. ilt.repaint(); frame.repaint(); //have to call one of these to get anything to display // ilt.paintComponent(frame.getGraphics()); //works frame.paintComponents(frame.getGraphics()); //works } //PRIVATE HELPER FUNCTIONS private String[] createImageFileNames(int count){ String[] fileNames = new String[count]; for(int i=0; i < count; i++) fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp"; return fileNames; } } 

paintComponent()没有在原始代码中调用的原因之一是因为组件具有“零大小”并且RepaintManger足够聪明,不会尝试绘制没有大小的东西。

重新排序代码的原因是因为当您将组件添加到框架然后使框架可见时,将调用布局管理器来布局组件。 默认情况下,一个框架使用BorderLayout,默认情况下,一个组件被添加到BorderLayout的中心,该组件会给予组件可用的所有空间,以便绘制它。

但是,您将内容窗格的布局管理器更改为FlowLayout,您仍然会遇到问题,因为FlowLayout会考虑组件的首选大小为零。

因此,您真正需要做的是为您的组件分配首选大小,以便布局管理员可以完成他们的工作。

这里的一个主要问题是你没有在Event Dispatch Thread(EDT)上更新你的swing组件。 尝试将main方法中的所有代码包装在以下内容中:

  SwingUtilities.invokeLater(new Runnable() { public void run() { // swing code here... } }); 

另外:在将帧设置为可见之前,将ImageLoadTest添加到帧中。 这是基于对代码的快速粗略读取 – 我将进一步阅读它,看看我还能找到什么。

编辑:

按照我上面的原始建议,简化主要方法,使其看起来如下,并调用paintComponent():

 public static void main(String args[]) throws InterruptedException { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("Empty JFrame"); frame.setSize(new Dimension(1000, 500)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); PaintComponentTest ilt = new PaintComponentTest(); frame.add(ilt); frame.setVisible(true); ilt.setBackground(Color.BLACK); } }); } 

此外,我会阅读使用计时器来执行动画,以及一般的Swing事件调度以及如何/何时覆盖各种绘制方法。

http://java.sun.com/products/jfc/tsc/articles/painting/

http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html

http://java.sun.com/docs/books/tutorial/uiswing/concurrency/dispatch.html

为了让Tom Hawtin感到高兴。 我再次重写了一遍

我改变了几件事(用//new评论检查行)

完全重写了它

  • 拆分成一个干净的新组件文件( ImageLoadTest.java )和一个测试它的文件( Tester.java

原始海报代码的改进

  • ImageLoadTest构造函数中调用父构造函数( super()
  • 提供了第二个构造函数来设置组件应显示的图像列表
  • 重要说明:在构造函数中setPreferredSize()组件的setPreferredSize() 。 如果没有设置尺寸,摆动当然不会涂抹您的组件。 首选大小基于最大值。 所有图像的宽度和所有图像高度的总和
  • 在覆盖paintComponent()调用super.paintComponent(g) paintComponent()
  • 更改paintComponent以自动将yOffset基于正在绘制的图像的高度

  • 在EDT上完成GUI初始化

  • 作为基于使用sleep()原始代码来说明加载和加载图像可能需要很长时间才能使用SwingWorker
  • worker等待然后设置新标题,然后加载图像
  • 完成后, done()worker最终将组件添加到JFrame并显示它。 如JFrame api中所述,向JFrame的内容窗格添加了组件。 并且正如javadoc中所描述的,在调用add()之后对JFrame进行了必要的调用validate() add() ,因为JFrame是一个已经可见的容器,其中子项已经更改。

来自validate() javdoc引文

validate方法用于使容器再次布置其子组件。 在显示容器后修改此容器的子组件(添加到容器中或从容器中删除,或更改布局相关信息)时,应调用此方法。

  • 第二个工人只是等待一些,然后将背景颜色设置为黑色
  • 使用JPanel作为ImageLoadTest基类来修复我无法使用JComponent setBackground()

所以你的主要问题是你没有设置组件的首选大小,并且在向已经可见的容器添加内容之后没有在JFrame上调用validate()

这应该工作

jpanelpaint / ImageLoadTest.java

 package jpanelpaint; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import javax.swing.JPanel; import java.util.List; public class ImageLoadTest extends JPanel { private List list; public ImageLoadTest() { super(); } public ImageLoadTest(List list) { this(); this.list = list; int height = 0; int width = 0; for (Image img : list) { height += img.getHeight(this); width = img.getWidth(this) > width ? img.getWidth(this) : width; setPreferredSize(new Dimension(width, height)); } } @Override protected void paintComponent(Graphics g) { int yOffset=0; super.paintComponent(g); System.err.println("ImageLoadTest.paintComponent()"); for(Image img : list) { g.drawImage(img, 0, yOffset, null); yOffset+=img.getHeight(this); } } } 

Tester.java

 import java.awt.Dimension; import java.awt.Color; import java.awt.Image; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.SwingWorker; import javax.swing.SwingUtilities; import java.util.List; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import jpanelpaint.ImageLoadTest; public class Tester { private JFrame frame; private ImageLoadTest ilt; private final int NUMBEROFFILES = 4; private List list; //will load the images SwingWorker worker = new SwingWorker, Void>() { @Override public List doInBackground() throws InterruptedException { //sleep at start so user is able to see empty jframe Thread.sleep(1000); //let Event-Dispatch-Thread (EDT) handle this SwingUtilities.invokeLater(new Runnable() { public void run() { frame.setTitle("Loading images"); } }); //sleep again so user is able to see loading has started Thread.sleep(1000); //loads the images and returns list return loadImages(); } @Override public void done() { //this is run on the EDT anyway try { //get result from doInBackground list = get(); frame.setTitle("Done loading images"); ilt = new ImageLoadTest(list); frame.getContentPane().add(ilt); frame.getContentPane().validate(); //start second worker of background stuff worker2.execute(); } catch (InterruptedException ignore) {} catch (ExecutionException e) { String why = null; Throwable cause = e.getCause(); if (cause != null) { why = cause.getMessage(); } else { why = e.getMessage(); } System.err.println("Error retrieving file: " + why); } } }; //just delay a little then set background SwingWorker worker2 = new SwingWorker() { @Override public List doInBackground() throws InterruptedException { Thread.sleep(1000); SwingUtilities.invokeLater(new Runnable() { public void run() { frame.setTitle("Setting background"); } }); Thread.sleep(1000); return null; } @Override public void done() { ilt.setBackground(Color.BLACK); frame.setTitle("Done!"); } }; public static void main(String args[]) { new Tester(); } public Tester() { //setupGUI SwingUtilities.invokeLater(new Runnable() { public void run() { frame = new JFrame("Empty JFrame"); frame.setSize(new Dimension(1000, 500)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); //start the swingworker which loads the images worker.execute(); } //create image names private String[] createImageFileNames(int count){ String[] fileNames = new String[count]; for(int i=0; i < count; i++) fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp"; return fileNames; } //load images private List loadImages() { List tmpA = new ArrayList(); try { for(String name : createImageFileNames(NUMBEROFFILES)){ System.err.println(name); tmpA.add(ImageIO.read(new File(name))); } } catch (IOException e) { } return tmpA; } } 

这些是原始代码导致它无法工作的主要问题:

  1. 在add()操作之后不调用validate()
  2. 没有设置组件的首选大小。
  3. 覆盖它时不调用super.paintComponent()(这使得setBackground()调用不起作用)
  4. 我需要从JPanelinheritance它才能被绘制。 即使在修复第3点时,Component和JComponent都不足以使setBackground()调用起作用。

完成上述操作后,调用方法paintComponent或paint无关紧要,只要我记得在开始时调用超级构造函数,两者似乎都能正常工作。

这些信息是根据@jitter,@ tackline和@camickr编写的,非常值得称赞!

PS不知道回答你自己的问题被认为是不好的forms,但由于我需要的信息来自几个答案,我认为最好的方法是修改其他答案并写出这样的总结。

我建议阅读“肮脏的富客户”的前几章。 我多年来一直在使用Swing,但只有在读完本书之后,我才能完全理解Java的绘画机制是如何工作的。