在过去的两天里,我一直试图理解 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;
}
}
答案 0 :(得分:11)
原始代码中不调用paintComponent()的原因之一是因为组件具有“零大小”,而RepaintManger足够聪明,不会尝试绘制没有大小的东西。
代码重新排序的原因是因为当您将组件添加到框架然后使框架可见时,将调用布局管理器来布局组件。默认情况下,一个框架使用BorderLayout,默认情况下,一个组件被添加到BorderLayout的中心,该组件会给予组件可用的所有空间,以便绘制它。
但是,您将内容窗格的布局管理器更改为FlowLayout,您仍然会遇到问题,因为FlowLayout会考虑组件的首选大小为零。
所以你真正需要做的是为你的组件分配一个首选大小,这样布局管理员就可以完成他们的工作。
答案 1 :(得分:4)
这里的一个主要问题是你没有更新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
答案 2 :(得分:4)
让汤姆·霍廷 - 行动感到高兴。我再次重写了
我改变了几件事(检查 //new
评论的行)
完全重写了
ImageLoadTest.java
)和要测试的文件(Tester.java
)原始海报代码的改进
ImageLoadTest
构造函数(super()
)setPreferredSize()
。如果没有设置尺寸,摆动当然不会涂抹你的组件。首选大小基于最大值。所有图像的宽度和所有图像高度的总和super.paintComponent(g)
paintComponent()
将paintComponent
更改为自动定位yOffset
正在绘制的图像高度
在EDT上完成GUI初始化
sleep()
来说明加载和加载图片的原始代码可能需要很长时间SwingWorker
才会被使用worker
等待然后设置新标题,然后加载图片worker
中的done()
最终将组件添加到JFrame
并显示它。如JFrame api中所述,向JFrame
的内容窗格添加了组件。正如javadoc中所述,在调用validate()
后对JFrame
add()
进行了必要的调用,因为JFrame
是已经可见的容器,这些容器已经更改了。来自validate()
的javdoc引文验证方法用于导致 容器布局其子组件 再次。应该在这时调用它 容器的子组件已修改 (添加到或删除 容器或与布局相关的 信息改变后) 容器已经显示。
JPanel
作为ImageLoadTest
的基类来修复setBackground()
我无法使用JComponent
。所以你的主要问题是你没有设置组件的首选大小,并且在向已经可见的容器添加内容之后没有在validate()
上调用JFrame
。
这应该有效
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;
}
}
答案 3 :(得分:3)
这些原始代码的主要问题导致它无法正常工作:
完成上述操作后,调用方法paintComponent或paint真的没关系,只要我记得在开始时调用超级构造函数,两者似乎都有效。
这个信息是从@jitter,@ tackline和@camickr写的那样汇编而成的,所以非常荣幸!
P.S。不知道回答你自己的问题是否被认为是不好的形式,但由于我需要的信息来自几个答案,我认为最好的方法是修改其他答案并写出这样的总结。
答案 4 :(得分:2)
我建议阅读“肮脏的富客户”的前几章。我多年来一直在使用Swing,但只有在阅读完这本书后,我才能完全理解Java的绘画机制是如何工作的。