【问题标题】:Why is paint()/paintComponent() never called?为什么从不调用paint()/paintComponent()?
【发布时间】:2010-12-13 04:02:42
【问题描述】:

在过去的两天里,我试图理解 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<Image> list;

 public ImageLoadTest() {
  list = new ArrayList<Image>();

  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;
 }
}

【问题讨论】:

    标签: java swing awt paint


    【解决方案1】:

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

    代码重新排序起作用的原因是,当您将组件添加到框架然后使框架可见时,会调用布局管理器来布局组件。默认情况下,框架使用 BorderLayout,并且默认情况下,组件被添加到 BorderLayout 的中心,这恰好为组件提供了所有可用空间,因此它被绘制。

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

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

    【讨论】:

      【解决方案2】:

      这里的一个主要问题是您没有更新Event Dispatch Thread (EDT) 上的挥杆组件。尝试将您的 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

      【讨论】:

      • 谢谢,会试试的。但是我必须在最后使用 setVisible 才能使其正常工作吗?尽早调用它的目的是看看我以后应该如何处理添加额外的图形元素。但是整个 Runnable 对我来说是新的。我在我看过的任何教程中都没有看到这一点(比如 zetcode.com/tutorials/javagamestutorial/movingsprites/… one)。
      • 使用 [java.awt.EventQueue.]invokeLater 让它对我有用。
      • (虽然这可能只是覆盖了一个错误。您应该在add 之后revalidate,如add 的API 文档中所述。)
      • 我认为将他的主要方法的东西包装在一个 invokeLater() 中并不是一个好主意。因为他有几个 sleep() 在那里会导致 EDT 挂起
      • 请注意,我将他的 Thread.sleep() 调用从 main 方法中取出,并建议阅读计时器来制作动画。我的回复的主要重点是让基础工作,并提供一些参考链接到更高级的主题,如动画、线程等。另外,汤姆的观点是,如果在设置之前添加组件,则不需要调用 revalidate在我的简化示例中可见。最后,我觉得这个问题有很多复杂的问题——希望我的回答有助于澄清一些问题或指出正确的方向。
      【解决方案3】:

      为了让 Tom Hawtin - tackline 开心。我又重写了一遍

      我更改了几处(检查带有//new 注释的行)

      完全重写

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

      对原始海报代码的改进

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

      • 在 EDT 上完成 GUI 初始化

      • 作为基于使用sleep() 来说明加载和加载图像的原始代码可能需要很长时间SwingWorker 的使用
      • worker 等待然后设置新标题,然后加载图片
      • 完成后,done() 中的worker 最终将组件添加到JFrame 并显示它。在JFrame 的内容窗格中添加了组件,如JFrame api 中所述。并且如 javadoc 中所述,在调用 add() 后,在 JFrame 上对 validate() 进行了必要的调用,因为 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<Image> list;
      
        public ImageLoadTest() {
          super();
        }
      
        public ImageLoadTest(List<Image> 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<Image> list;
      
        //will load the images
        SwingWorker worker = new SwingWorker<List<Image>, Void>() {
          @Override
          public List<Image> 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<image>
            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<Object, Void>() {
          @Override
          public List<Image> 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<Image> loadImages() {
          List<Image> tmpA = new ArrayList<Image>();
          try {
            for(String name : createImageFileNames(NUMBEROFFILES)){
              System.err.println(name);
              tmpA.add(ImageIO.read(new File(name)));
            }
          } catch (IOException e) { }
      
          return tmpA;
        }
      }
      

      【讨论】:

      • 你还在 EDT 上做 Swing 的东西!
      • 感谢您指出显而易见的事情。 8|不是因为我解决了问题海报的实际问题(图像+背景未显示)而对我投赞成票,而是因为另一张海报已经解释过的事情而对我投反对票。我认为至少那位 oligofren 可以自己做/集成到我的解决方案中
      • 哇,抖动,修复一个单纯的概念验证代码需要做很多工作!可能有点矫枉过正,但无论如何,非常感谢。我使用您的代码修复了原始代码中的主要问题:1)在 add() 操作后未调用 validate() 2) 未设置组件的首选大小。 3)在覆盖 super.paintComponent() 时不调用它(这使得 setBackground() 调用不起作用。这样做之后,一切都按预期工作。编辑原始帖子以反映这一点。
      【解决方案4】:

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

      1. 在 add() 操作后不调用 validate()
      2. 未设置组件的首选大小。
      3. 覆盖时不调用 super.paintComponent()(这使得 setBackground() 调用不起作用)
      4. 我需要从 JPanel 继承才能对其进行绘制。 Component 和 JComponent 都不足以使 setBackground() 调用正常工作,即使在修复点 3 时也是如此。

      做完以上,调用paintComponent或paint方法真的没关系,只要我记得一开始就调用超级构造函数,两者似乎都可以工作。

      这些信息是由 @jitter、@tackline 和 @camickr 所写的内容汇总而成,非常荣幸!

      附:不知道回答你自己的问题是否被认为是不好的形式,但由于我需要的信息是从几个答案中收集的,我认为最好的方法是升级其他答案并像这样写一个总结。

      【讨论】:

        【解决方案5】:

        我建议阅读“肮脏的富客户”的前几章。我使用 Swing 多年,但直到读了这本书后,我才完全明白 Java 的绘画机制是如何工作的。

        【讨论】:

        • 这是一个很好的提示。后来我真的读了。很棒的书,尽管 GUI 作品在这个网络世界中似乎是九十年代后期的东西。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-17
        • 1970-01-01
        • 1970-01-01
        • 2013-05-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多