2009-11-04 19 views
8

Durante los últimos dos días he intentado comprender cómo maneja Java los gráficos, pero han fallado miserablemente en eso. Mi principal problema es entender exactamente cómo y cuándo se llama/debería llamarse paint() (o el paintComponent() más nuevo).¿Por qué paint()/paintComponent() nunca se llama?

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

Cambié el nombre del método paint() a paintComponent() con la esperanza de que solucionaría mi problema de que nunca se llamara (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; 
} 
} 

Respuesta

3

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

  1. not c alling validate() después de una operación add()
  2. sin configurar 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 dos componentes ni JComponent fue suficiente para que el setBackground() para trabajar, incluso cuando la fijación de punto 3.

vez hecho lo anterior, realmente no importa si se llama el método de paintComponent o pintura, ambos parecían trabajar siempre que recuerde llamar al súper constructor desde el principio.

Esta información fue recopilada de lo que @jitter, @tackline, y @camickr escribieron, ¡así que grandes felicitaciones!

P.S. No tengo idea si responder a tu propia pregunta se considera de mala calidad, pero como la información que necesitaba se compilaba a partir de varias respuestas, pensé que la mejor manera era actualizar las otras respuestas y escribir un resumen como este.

4

Una cuestión importante aquí es que no se está actualizando sus componentes swing en el Event Dispatch Thread (EDT). Trate de envolver todo el código en el método principal en el siguiente:

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

también: añadir su ImageLoadTest en el bastidor antes de establecer el marco visible. Esto se basa en una lectura rápida del código: lo leeré más a fondo y veré qué más puedo encontrar.

EDIT:

Siga mi consejo de origen encima, y ​​simplificar su principal método para parecerse a lo siguiente y su paintComponent() será llamado:

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 me gustaría leer sobre utilizar temporizadores para realizar animaciones, así como 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

+0

Gracias, lo intentaré. Pero, ¿debo usar setVisible al final para que funcione? El punto de llamarlo temprano era ver cómo debería manejar la adición de elementos gráficos adicionales en un momento posterior. Pero todo lo de Runnable era nuevo para mí; No he visto eso en ninguno de los tutoriales que he visto (como this one). – oligofren

+0

El uso de ['java.awt.EventQueue']' invokeLater' lo hace funcionar para mí. –

+1

(Aunque eso probablemente solo cubra un error. Deberías 'revalidar' después de' agregar' como se describe en los documentos de la API para 'agregar'.) –

2

recomiendo la lectura del primer par de capítulos de "forrarse Clientes". Había estado usando Swing durante años, pero solo después de leer este libro finalmente pude entender completamente cómo funciona el mecanismo de pintura de Java.

+0

Eso fue un buen consejo. De hecho, lo leí más tarde. Fantástico libro, aunque las obras de GUI parecen cosa de finales de los noventa en este mundo de la web. – oligofren

11

Una de las razones por las que paintComponent() no se invoca en el código original es porque el componente tiene un "tamaño cero" y el RepaintManger es lo suficientemente inteligente como para no intentar pintar algo sin tamaño.

La razón por la que el reordenamiento del código funciona es porque cuando agrega el componente al marco y luego lo hace visible, se invoca el administrador de disposición para diseñar el componente. De forma predeterminada, un marco utiliza BorderLayout y, de forma predeterminada, se agrega un componente al centro de BorderLayout, que da todo el espacio disponible al componente para que se pueda pintar.

Sin embargo, cambia el administrador de diseño del panel de contenido para que sea FlowLayout, aún así podría tener un problema porque FlowLayout respeta el tamaño preferido del componente que es cero.

Entonces, lo que realmente necesita hacer es asignarle un tamaño preferido a su componente para que los administradores del diseño puedan hacer su trabajo.

4

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

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

reescribió por completo

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

Mejoras en código de carteles originales

llamar a setPreferredSize() del componente en el constructor:
  • llamada al constructor de los padres en ImageLoadTest constructor (super())
  • proporcionado segundo constructor a la lista de imágenes que componente debe mostrar
  • IMPORTANTE establecido. Si el tamaño no está configurado, oscilar, por supuesto, no pintará su componente. el tamaño preferido se basa en max. ancho de todas las imágenes y en la suma de todas las alturas imagen
  • llamada a super.paintComponent(g) en overriden paintComponent()
  • cambió paintComponent basar automáticamente yOffset en la altura de imágenes está elaborando inicialización

  • GUI hecho en EDT

  • como el código original basado en el uso de sleep() para ilustrar la carga y carga de imágenes podría tomar mucho tiempo SwingWorker se usan
  • worker espera y luego establece un nuevo título y luego carga las imágenes
  • al finalizar el worker en done(), finalmente agrega el componente al JFrame y lo muestra. Se agregó componente al panel de contenido de JFrame como se describe en JFrame api. Y como se describe en javadoc hizo la llamada necesaria al validate() en JFrame después de llamar al add(), ya que el JFrame es un contenedor ya visible cuyos hijos cambiaron.

javdoc cita de validate()

Se utiliza el método de validación para causar un recipiente para diseñar sus subcomponentes de nuevo. Se debe invocar cuando se modifiquen los subcomponentes del contenedor (agregado o eliminado del contenedor , o cambie la información relacionada con la disposición ) después de que se haya mostrado el contenedor .

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

por lo 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; 
    } 
} 
+0

¡Todavía estás haciendo cosas de Swing en el EDT! –

+0

Gracias por señalar lo obvio.8 | En vez de subirme la votación porque resolví los problemas reales del cartel de la pregunta (las imágenes + el fondo no se muestran), me votas por algo que ya se ha explicado. Pensé que al menos ese bit oligofren podría hacer/integrar en mi solución por sí mismo – jitter

+0

Guau, ¡jitter, eso es mucho trabajo para arreglar un simple código de prueba de concepto! Un poco exagerado, tal vez, pero un gran GRACIAS, de todos modos. Utilicé su código para corregir los problemas principales en el código original: 1) no llamando a validate() después de una operación de agregar() 2) no estableciendo el tamaño preferido del componente. 3) no se llama a super.paintComponent() al anularlo (esto hizo que la llamada a setBackground() no funcione. Después de hacer eso, todo funciona según lo previsto. editando la publicación original para reflejar esto. – oligofren

Cuestiones relacionadas