Почему paint() / paintComponent() никогда не вызывается?

StackOverflow https://stackoverflow.com/questions/1676187

  •  16-09-2019
  •  | 
  •  

Вопрос

В течение последних двух дней я пытался понимать как 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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top