Pregunta

Durante los últimos dos días he intentado entender cómo Java maneja los gráficos, pero han fracasado estrepitosamente precisamente en eso.Mi principal problema es entender exactamente cómo y cuándo se debe llamar a paint() (o al nuevo paintComponent() ).

En el siguiente código que hice para ver cuándo se crean las cosas, nunca se llama a paintComponent(), a menos que yo mismo le agregue una llamada manualmente o llame a JFrame.paintAll()/JFrame.paintComponents().

Cambié el nombre del método paint() a paintComponent() con la esperanza de que eso solucionara mi problema de que nunca lo llamaran (incluso en repaint()), pero no tuve suerte.

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;
 }
}
¿Fue útil?

Solución 4

Estos fueron los principales problemas con el código original que causó que no funcione:

  1. No llamar a validate () después de una operación de adición ()
  2. no establecer el tamaño preferido del componente.
  3. No llamar super.paintComponent () cuando sea redefinido (esto hizo que el setBackground () llamada no trabajar)
  4. que tenía que heredar de JPanel con el fin a que se haga pintado. Ninguno de los componentes ni JComponent era suficiente para la setBackground () para trabajar, incluso cuando la fijación de punto 3.

Una vez hecho lo anterior, realmente no importa si se llama al método paintComponent o pintura, tanto parecía funcionar todo el tiempo que recordaba que llamar al constructor de la superclase en la salida.

Esta información se ensambla a partir de lo que @jitter, @tackline y @camickr escribió, tan grandes felicitaciones!

P.S. Ni idea de si respondiera a su propia pregunta se considera de mala, pero ya que la información que necesitaba se monta a partir de varias respuestas, pensé que la mejor manera se upmodding las otras respuestas y escribir una suma de esta manera.

Otros consejos

Una de las razones del paintComponent () no consigue invocado en el código original se debe a que el componente tiene una "talla cero" y el RepaintManger es lo suficientemente inteligente como para no tratar de pintar algo sin cantidad.

La razón por la reordenación del código funciona es porque cuando se añade el componente en el bastidor y luego hacer el marco visible el controlador de distribución se invoca a la disposición del componente. Por defecto un marco utiliza un BorderLayout y por defecto de un componente se añade al centro de la BorderLayout lo que ocurre darle todo el espacio disponible para el componente para que se pintó.

Sin embargo, cambiar el controlador de distribución del panel de contenido a ser un FlowLayout, todavía tendría un problema porque un FlowLayout respeta el tamaño preferido del componente que es cero.

Así que lo que realmente necesita hacer es asignar un tamaño preferido para que su componente de modo controladores de distribución pueden hacer su trabajo.

Un problema importante aquí es que no está actualizando los componentes de su swing en el Hilo de envío de eventos (EDT).Intente empaquetar todo el código en su método principal de la siguiente manera:

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            // swing code here...             
        }
    });

También:agregue su ImageLoadTest al marco antes de configurar el marco como visible.Esto se basa en una lectura rápida y superficial del código; lo leeré más y veré qué más puedo encontrar.

EDITAR:

Siga mi consejo original anterior y simplifique su método principal para que se vea como el siguiente y se llamará a su 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);
        }
    });
}

También leería sobre el uso de temporizadores para realizar animaciones, así como sobre el envío general de eventos Swing y cómo/cuándo anular varios métodos de pintura.

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

Para hacer Tom Hawtin - tackline feliz. He reescrito una vez más

Hay varias cosas que ha cambiado (compruebe las líneas con el comentario //new)

reescribió por completo

  • Dividir en un archivo limpio nuevo componente (ImageLoadTest.java) y un archivo para probarlo (Tester.java)

Las mejoras en el código original de carteles

  • llamada al constructor del padre en el constructor ImageLoadTest (super())
  • proporcionado segundo constructor a la lista de imágenes que componente debe mostrar
  • set
  • IMPORTANTE: llamar a setPreferredSize() del componente en el constructor. Si el tamaño no se ajusta oscilación, por supuesto, no va a pintar su componente. tamaño preferido se basa en max. ancho de todas las imágenes y en la suma de todas las alturas de imagen
  • llamar a super.paintComponent(g) en paintComponent() overriden
  • paintComponent cambiado a yOffset base de forma automática de la altura de las imágenes está elaborando

  • inicialización GUI hecho en EDT

  • como código original basado en el uso de sleep() para ilustrar la carga y carga de imágenes podría llevar mucho tiempo se utilizan de SwingWorker
  • worker Esperas a continuación, establece un nuevo título y luego carga las imágenes
  • al finalizar el worker en done() finalmente se añade el componente al JFrame y lo muestra. componente al panel de contenido de JFrame añadido como se describe en JFrame api . Y como se describe en javadoc hecho necesario llamar a validate() en JFrame después de llamar add(), como el JFrame es un contenedor ya visible whichs cambiaron los niños.
  

javdoc cita de validate()

     

El método de validación se utiliza para causar una   contenedores para disponer de sus subcomponentes   de nuevo. Se debe invocarse cuando este   subcomponentes de contenedores se modifican   (Añadido o retirado de la   recipiente, o la disposición relacionada   la información ha cambiado) después de la   contenedor ha sido mostrada.

  • segundo trabajador simplemente no esperar un poco más a continuación, establece el color de fondo a negro
  • JPanel utiliza como clase base para fijar ImageLoadTest setBackground() que no podía llegar a trabajar con JComponent.

Así que sus principales problemas en los que no se estableció el tamaño preferido del componente y que no llamó validate() en el JFrame después de añadir algo al contenedor ya visible.

Esto debería funcionar

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;
  }
}

recomiendo la lectura del primer par de capítulos de "forrarse Clientes". Había estado utilizando oscilación durante años, pero sólo después de leer este libro tenía por fin entiendo completamente cómo funciona exactamente el mecanismo pintura de Java.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top