Почему 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 ответов

Решение

Это были основные проблемы с исходным кодом, из-за которых он не работал:

  1. не вызывает validate() после операции add()
  2. не устанавливая предпочтительный размер компонента.
  3. не вызывая super.paintComponent() при переопределении (это заставило вызов setBackground() не работать)
  4. Мне нужно было унаследовать от 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.

Другие вопросы по тегам