Почему 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;
}
}
Решение 4
Это были основные проблемы с исходным кодом, из-за которых он не работал:
- не вызывать validate() после операции add()
- не задан предпочтительный размер компонента.
- не вызывая Super.paintComponent() при переопределении его (это сделал setBackground() вызов не будет работать)
- Мне нужно было наследовать от JPanel, чтобы он был раскрашен.Ни Component, ни JComponent не были достаточными для работы вызова setBackground(), даже при исправлении точки 3.
Сделав вышесказанное, на самом деле не имело значения, вызывал ли метод paintComponent или paint, оба, казалось, работали до тех пор, пока я не забыл вызвать суперконструктор в начале.
Эта информация была собрана из того, что написали @jitter, @tackline и @camickr, так что большая честь!
P.S.Понятия не имею, считается ли дурным тоном отвечать на ваш собственный вопрос, но поскольку нужная мне информация была собрана из нескольких ответов, я подумал, что лучшим способом было бы доработать другие ответы и подвести итог следующим образом.
Другие советы
Одна из причин, по которой метод PaintComponent() не вызывается в исходном коде, заключается в том, что компонент имеет «нулевой размер», а RepaintManger достаточно умен, чтобы не пытаться нарисовать что-то без размера.
Причина, по которой изменение порядка кода работает, заключается в том, что когда вы добавляете компонент в фрейм, а затем делаете фрейм видимым, вызывается менеджер компоновки для компоновки компонента.По умолчанию фрейм использует BorderLayout, и по умолчанию компонент добавляется в центр BorderLayout, что дает компоненту все доступное пространство для его рисования.
Однако если вы измените менеджер макета панели содержимого на FlowLayout, у вас все равно возникнет проблема, поскольку FlowLayout учитывает предпочтительный размер компонента, который равен нулю.
Итак, что вам действительно нужно сделать, так это назначить предпочтительный размер вашего компонента, чтобы менеджеры по расположению могли выполнять свою работу.
Одна из основных проблем здесь заключается в том, что вы не обновляете компоненты Swing на Поток отправки событий (EDT).Попробуйте обернуть весь код вашего основного метода следующим образом:
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
Чтобы сделать Том Хотин - линия захвата счастливый.Я переписал еще раз
Есть несколько вещей, которые я изменил (проверьте строки с помощью //new
комментарий)
Переписал его полностью
- Разделить на чистый новый файл компонента (
ImageLoadTest.java
) и файл для его тестирования (Tester.java
)
Улучшения в исходном коде плакатов
- вызов конструктора родительского элемента в
ImageLoadTest
конструктор (super()
) - предоставлен второй конструктор для задания списка изображений, которые должен отображать компонент
- ВАЖНЫЙ:позвоните в
setPreferredSize()
компонента в конструкторе.Если размер не задан, swing, конечно, не будет рисовать ваш компонент.предпочтительный размер зависит от макс.ширина всех изображений и сумма всех высот изображений - позвоните в
super.paintComponent(g)
в переопределенномpaintComponent()
измененный
paintComponent
для автоматического создания базыyOffset
по высоте рисуемых изображенийИнициализация графического интерфейса выполнена в EDT
- в качестве исходного кода, основанного на использовании
sleep()
для иллюстрации загрузки изображений может потребоваться много времениSwingWorker
используются worker
ожидает, затем устанавливает новый заголовок, а затем загружает изображения- по завершении
worker
вdone()
наконец, добавляет компонент вJFrame
и отображает это.Добавлен компонент в панель содержимогоJFrame
как описано в JFrame API.И, как описано в javadoc, сделал необходимый вызовvalidate()
вкл .JFrame
после звонкаadd()
, посколькуJFrame
это уже видимый контейнер, который изменили дочерние элементы.
цитата из javdoc
validate()
Метод validate используется для того, чтобы заставить контейнер снова разложить свои подкомпоненты .Он должен вызываться, когда изменяются подкомпоненты этого контейнера (добавляются в контейнер или удаляются из него, или изменяется информация, связанная с макетом ) после отображения контейнера.
- второй рабочий просто выполняет еще некоторое ожидание, затем устанавливает цвет фона на черный
- использованный
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;
}
}
Рекомендую прочитать первые пару глав «Неприлично богатых клиентов».Я использовал Swing в течение многих лет, но только после прочтения этой книги я, наконец, полностью понял, как именно работает механизм рисования Java.