Java 为什么从不调用paint()/paintComponent()?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1676187/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 21:16:55  来源:igfitidea点击:

Why is paint()/paintComponent() never called?

javaswingawtpaint

提问by oligofren

For the last two days I have tried to understandhow Java handles graphics, but have failed miserably at just that. My main problem is understanding exactly how and when paint() (or the newer paintComponent() ) is/should be called.

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

In the following code I made to see when things are created, the paintComponent() is never called, unless I manually add a call to it myself or calls to JFrame.paintAll()/JFrame.paintComponents().

在下面的代码中,我查看创建对象的时间,从不调用paintComponent(),除非我自己手动添加对它的调用或调用JFrame.paintAll()/JFrame.paintComponents()。

I renamed the paint() method to paintComponent() in hoping that would fix my problem of it never being called (even at repaint()), but no luck.

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

采纳答案by oligofren

These were the main problems with the original code that caused it not to work:

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

  1. not calling validate() after an add() operation
  2. not setting the preferred size of the component.
  3. not calling super.paintComponent() when overriding it (this made the setBackground() call not work)
  4. I needed to inherit from JPanel in order for it to get painted. Neither Component nor JComponent was sufficient for the setBackground() call to work, even when fixing point 3.
  1. 在 add() 操作后不调用 validate()
  2. 未设置组件的首选大小。
  3. 覆盖它时不调用 super.paintComponent() (这使得 setBackground() 调用不起作用)
  4. 我需要从 JPanel 继承才能让它被绘制。Component 和 JComponent 都不足以让 setBackground() 调用工作,即使在修复点 3 时也是如此。

Having done the above, it really didn't matter if calling the method paintComponent or paint, both seemed to work as long as I remembered to call the super constructor at the start.

完成上述操作后,调用方法paintComponent 或paint 真的没有关系,只要我记得在开始时调用超级构造函数,两者似乎都可以工作。

This info was assembled from what @jitter, @tackline, and @camickr wrote, so big kudos!

这些信息是由@jitter、@tackline 和@camickr 所写的内容汇总而成的,太赞了!

P.S. No idea if answering your own question is considered bad form, but since the information I needed was assembled from several answers, I thought the best way was upmodding the other answers and writing a sum up like this.

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

回答by Chris B.

One major issue here is you are not updating your swing components on the Event Dispatch Thread (EDT). Try wrapping all the code in your main method in the following:

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

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

Also: add your ImageLoadTest to the frame before setting the frame visible. This is based on a quick cursory read of the code -- I will read it further and see what else I can find.

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

EDIT:

编辑:

Follow my original advice above, and simplify your main method to look like the following and your paintComponent() will be called:

遵循我上面的原始建议,并将您的主要方法简化为如下所示,您的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);
        }
    });
}

Also I would read up on using timers to perform animation, as well as general Swing event dispatching and how/when to override various paint methods.

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

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

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/misc/timer.html

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

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

回答by Steve McLeod

I recommend reading the first couple of chapters of "Filthy Rich Clients". I had been using Swing for years, but only after reading this book did I finally fully understand exactly how Java's painting mechanism works.

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

回答by camickr

One of the reasons the paintComponent() doesn't get invoked in the original code is because the component has a "zero size" and the RepaintManger is smart enough not to try and paint something with no size.

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

The reason the reordering of the code works is because when you add the component to the frame and then make the frame visible the layout manager is invoked to layout the component. By default a frame uses a BorderLayout and by default a component is added to the center of the BorderLayout which happens give all the space available to the component so it gets painted.

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

However, you change the layout manager of the content pane to be a FlowLayout, you would still have a problem because a FlowLayout respects the preferred size of the component which is zero.

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

So what you really need to do is assign a preferred size to you your component so layout managers can do their job.

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

回答by jitter

To make Tom Hawtin - tacklinehappy. I rewrote once again

为了让汤姆霍廷 - 铲球高兴。我又重新写了一遍

There are several things I changed (check the lines with the //newcomment)

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

Rewrote it completely

彻底改写

  • Split into a clean new component file (ImageLoadTest.java) and a file to test it (Tester.java)
  • 拆分为一个干净的新组件文件 ( ImageLoadTest.java) 和一个用于测试它的文件 ( Tester.java)

Improvements on original posters code

原始海报代码的改进

  • call constructor of parent in ImageLoadTestconstructor (super())
  • provided second constructor to set list of images which component should display
  • IMPORTANT: call to setPreferredSize()of component in constructor. If size isn't set swing of course won't paint your component. preferred size is based on max. width of all images and on sum of all image heights
  • call to super.paintComponent(g)in overriden paintComponent()
  • changed paintComponentto automatically base yOffseton height of images being drawn

  • GUI initialization done on EDT

  • as original code based on using sleep()to illustrate loading and loading of images could take a long time SwingWorker's are used
  • workerwaits then sets new title and then loads images
  • on completion the workerin done()finally adds the component to the JFrameand displays it. Added component to content pane of JFrameas described in JFrameapi. And as described in javadoc made necessary call to validate()on JFrameafter calling add(), as the JFrameis an already visible container whichs children changed.
  • ImageLoadTest构造函数 ( super()) 中调用父的构造函数
  • 提供了第二个构造函数来设置组件应该显示的图像列表
  • 重要:setPreferredSize()在构造函数中调用组件。如果未设置大小,swing 当然不会绘制您的组件。首选尺寸基于最大值。所有图像的宽度和所有图像高度的总和
  • 调用super.paintComponent(g)in 覆盖paintComponent()
  • 更改paintComponentyOffset根据正在绘制的图像的高度自动

  • 在 EDT 上完成 GUI 初始化

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

javdoc citation from validate()

The validate method is used to cause a container to lay out its subcomponents again. It should be invoked when this container's subcomponents are modified (added to or removed from the container, or layout-related information changed) after the container has been displayed.

javdoc 引用自 validate()

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

  • second worker just does some more waiting then sets background color to black
  • used JPanelas baseclass for ImageLoadTestto fix setBackground()which I couldn't get to work with JComponent.
  • 第二个工人只是做更多的等待然后将背景颜色设置为黑色
  • 用作JPanel基类ImageLoadTest来修复setBackground()我无法使用的JComponent.

So your main problems where that you didn't set the preferred size of the component and that you did not call validate()on the JFrameafter adding something to the already visible container.

所以,你在那里,你没有设置组件的首选大小和你没有打电话主要问题validate()JFrame增加一些对已经显现容器后。

This should work

这应该工作

jpanelpaint/ImageLoadTest.java

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