Почему 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;
}
}
5 ответов
Это были основные проблемы с исходным кодом, из-за которых он не работал:
- не вызывает validate() после операции add()
- не устанавливая предпочтительный размер компонента.
- не вызывая super.paintComponent() при переопределении (это заставило вызов setBackground() не работать)
- Мне нужно было унаследовать от JPanel для того, чтобы его нарисовали. Ни Component, ни JComponent не было достаточно для вызова setBackground(), даже при исправлении точки 3.
Сделав вышесказанное, действительно не имело значения, вызывал ли метод paintComponent или paint, оба они, казалось, работали до тех пор, пока я вспомнил, как вызывать супер-конструктор в начале.
Эта информация была взята из того, что написали @jitter, @tackline и @camickr, такие большие почести!
PS Понятия не имею, если ответ на ваш собственный вопрос считается дурным тоном, но поскольку необходимая мне информация была собрана из нескольких ответов, я подумал, что лучший способ - это изменить другие ответы и написать такую сумму, как эта.
Одна из причин, по которой paintComponent() не вызывается в исходном коде, заключается в том, что компонент имеет "нулевой размер", а RepaintManger достаточно умен, чтобы не пытаться рисовать что-либо без размера.
Причина, по которой переупорядочение кода работает, заключается в том, что когда вы добавляете компонент к фрейму, а затем делаете его видимым, менеджер компоновки вызывается для компоновки компонента. По умолчанию фрейм использует BorderLayout, и по умолчанию компонент добавляется в центр BorderLayout, что дает все пространство, доступное компоненту, чтобы он был закрашен.
Однако вы измените диспетчер компоновки панели содержимого на FlowLayout, у вас все равно будет проблема, потому что FlowLayout учитывает предпочтительный размер компонента, который равен нулю.
Так что вам действительно нужно назначить предпочтительный размер для вашего компонента, чтобы менеджеры по расположению могли выполнять свою работу.
Одна из основных проблем заключается в том, что вы не обновляете свои компоненты свинга в потоке рассылки событий (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
Чтобы сделать Тома Хотина счастливым. Я переписал еще раз
Есть несколько вещей, которые я изменил (проверьте строки с //new
комментарий)
Переписал его полностью
- Разделить на чистый новый файл компонента (
ImageLoadTest.java
) и файл для его проверки (Tester.java
)
Улучшения оригинального кода постеров
- вызвать конструктор родителя в
ImageLoadTest
конструктор (super()
) - предоставлен второй конструктор для установки списка изображений, который компонент должен отображать
- ВАЖНО: позвоните в
setPreferredSize()
компонента в конструкторе. Если размер не установлен, то, разумеется, не будет окрашивать ваш компонент. предпочтительный размер основан на макс. ширина всех изображений и сумма всех высот изображения - позвонить
super.paintComponent(g)
в переопределенииpaintComponent()
изменено
paintComponent
для автоматической базыyOffset
по высоте рисуемых изображенийGUI инициализация выполнена на 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;
}
}
Я рекомендую прочесть первые несколько глав "Filthy Rich Clients". Я использовал Swing в течение многих лет, но только прочитав эту книгу, я наконец полностью понял, как работает механизм рисования в Java.