为什么 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<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;
}
}
For the last two days I have tried to understand how 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.
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().
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.
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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
PaintComponent() 在原始代码中没有被调用的原因之一是因为该组件具有“零大小”,并且 RepaintManger 足够聪明,不会尝试绘制没有大小的东西。
代码重新排序起作用的原因是,当您将组件添加到框架然后使框架可见时,将调用布局管理器来布局组件。默认情况下,框架使用 BorderLayout,并且默认情况下将组件添加到 BorderLayout 的中心,这会为组件提供所有可用空间,以便对其进行绘制。
但是,您将内容窗格的布局管理器更改为 FlowLayout,仍然会遇到问题,因为 FlowLayout 遵循组件的首选大小为零。
因此,您真正需要做的是为您的组件分配一个首选尺寸,以便布局管理器可以完成他们的工作。
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.
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.
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.
So what you really need to do is assign a preferred size to you your component so layout managers can do their job.
这里的一个主要问题是您没有在 Event 上更新 swing 组件调度线程 (EDT)。尝试将所有代码包装在主方法中,如下所示:
另外:在将框架设置为可见之前,将 ImageLoadTest 添加到框架中。这是基于对代码的快速粗略阅读——我将进一步阅读它,看看我还能找到什么。
编辑:
遵循我上面的原始建议,并将您的主要方法简化为如下所示,并且您的paintComponent()将被调用:
此外,我还会阅读有关使用计时器执行动画以及一般方法的内容Swing 事件调度以及如何/何时覆盖各种绘制方法。
http://java.sun.com/products/jfc/tsc/articles/绘画/
http://java.sun.com /docs/books/tutorial/uiswing/misc/timer.html
http://java.sun.com/docs/books/tutorial/uiswing/concurrency/dispatch.html
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:
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.
EDIT:
Follow my original advice above, and simplify your main method to look like the following and your paintComponent() will be called:
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.
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()
。如果未设置尺寸,那么 swing 当然不会绘制您的组件。首选尺寸基于最大尺寸。所有图像的宽度以及所有图像高度的总和paintComponent()
中的super.paintComponent(g)
更改
paintComponent
自动将yOffset
基于正在绘制的图像的高度GUI 初始化在 EDT 上完成
sleep()
来说明加载和加载图像可能需要很长时间SwingWorker
使用worker
等待,然后设置新标题,然后done()
中的worker
最后将组件添加到 < code>JFrame 并显示它。将组件添加到JFrame
的内容窗格,如 JFrame API。正如 javadoc 中所述,在调用add()
后,对JFrame
上的validate()
进行了必要的调用,因为JFrame
> 是一个已经可见的容器,其子级已更改。JPanel
作为ImageLoadTest
的基类来修复我无法解决的setBackground()
开始使用JComponent
。因此,您的主要问题是您没有设置组件的首选大小,并且在向已经可见的内容添加某些内容后没有在
JFrame
上调用validate()
容器。这应该可以工作
jpanelpaint/ImageLoadTest.java
Tester.java
To make Tom Hawtin - tackline happy. I rewrote once again
There are several things I changed (check the lines with the//new
comment)Rewrote it completely
ImageLoadTest.java
) and a file to test it (Tester.java
)Improvements on original posters code
ImageLoadTest
constructor (super()
)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 heightssuper.paintComponent(g)
in overridenpaintComponent()
changed
paintComponent
to automatically baseyOffset
on height of images being drawnGUI initialization done on EDT
sleep()
to illustrate loading and loading of images could take a long timeSwingWorker
's are usedworker
waits then sets new title and then loads imagesworker
indone()
finally adds the component to theJFrame
and displays it. Added component to content pane ofJFrame
as described in JFrame api. And as described in javadoc made necessary call tovalidate()
onJFrame
after callingadd()
, as theJFrame
is an already visible container whichs children changed.JPanel
as baseclass forImageLoadTest
to fixsetBackground()
which I couldn't get to work withJComponent
.So your main problems where that you didn't set the preferred size of the component and that you did not call
validate()
on theJFrame
after adding something to the already visible container.This should work
jpanelpaint/ImageLoadTest.java
Tester.java
这些是导致其无法工作的原始代码的主要问题:
setBackground() 调用不起作用)
完成上述操作后,调用 PaintComponent 或 Paint 方法实际上并不重要,只要我记得,两者似乎都可以工作在开始时调用超级构造函数。
此信息是根据 @jitter、@tackline 和 @camickr 撰写的内容汇总的,非常值得称赞!
PS 不知道回答你自己的问题是否被认为是不好的形式,但由于我需要的信息是由几个答案组合而成的,我认为最好的方法是更新其他答案并写出这样的总结。
These were the main problems with the original code that caused it not to work:
setBackground() call not work)
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.
This info was assembled from what @jitter, @tackline, and @camickr wrote, so big kudos!
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.
我建议阅读《肮脏的富客户》的前几章。我已经使用Swing很多年了,但直到读了这本书之后,我才终于完全理解了Java的绘画机制是如何工作的。
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.