Pergunta

Nos últimos dois dias tenho tentado entender como Java lida com gráficos, mas falharam miseravelmente em apenas isso. Meu principal problema é entender exatamente como e quando paint () (ou o mais recente paintComponent ()) é / deve ser chamado.

No código a seguir eu fiz para ver quando as coisas são criadas, o paintComponent () nunca é chamado, a menos que eu adicionar manualmente uma chamada para ele próprio ou chamadas para JFrame.paintAll () / JFrame.paintComponents ().

Eu renomeou o método paint () para paintComponent () na esperança de que iria resolver o meu problema de nunca ser chamado (mesmo em repaint ()), mas sem sorte.

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

Solução 4

Estes foram os principais problemas com o código original que causaram não ao trabalho:

  1. não chamar validate () após uma operação add ()
  2. não definir o tamanho preferido do componente.
  3. Não chamando super.paintComponent () quando substituindo-lo (isso fez a setBackground () chamada não trabalho)
  4. que eu precisava para herdar de JPanel para que ele para se pintou. Nem Component nem JComponent foi suficiente para a chamada setBackground () para trabalho, mesmo quando da fixação do ponto 3.

Depois de ter feito o exposto, ele realmente não importa se chamar o método paintComponent ou pintar, ambos pareciam trabalhar enquanto eu me lembrava de chamar o super construtor no início.

Esta informação foi montada a partir do que @jitter, @tackline e @camickr escreveu, tão grandes elogios!

P.S. Não faço ideia se responder à sua própria pergunta é considerado uma forma ruim, mas desde que a informação que eu precisava foi montado a partir de várias respostas, eu pensei que a melhor maneira foi upmodding as outras respostas e escrever uma soma-se assim.

Outras dicas

Uma das razões do paintComponent () não se invocados no código original é porque o componente tem um "tamanho zero" eo RepaintManger é inteligente o suficiente para não tentar pintar algo sem tamanho.

A razão para a reorganização dos trabalhos de código é porque quando você adiciona o componente ao quadro e, em seguida, fazer o quadro visível o gerenciador de layout é invocado para o layout do componente. Por padrão um quadro usa um BorderLayout e, por padrão um componente é adicionado ao centro do BorderLayout que acontece dar todo o espaço disponível para o componente para que ele obtém pintado.

No entanto, você alterar o gerenciador de layout do painel de conteúdo para ser um FlowLayout, você ainda teria um problema porque um FlowLayout respeita o tamanho preferido do componente que é zero.

Então, o que você realmente precisa fazer é atribuir um tamanho preferido para você seu componente para que os gerentes de layout pode fazer o seu trabalho.

Uma questão importante aqui é que você não está atualizando seus componentes swing no Despacho Evento thread (EDT) . Tentar envolver todo o código em seu método principal no seguinte:

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

Além disso: adicione seu ImageLoadTest para o quadro antes de definir o quadro visível. Isto é baseado em uma leitura superficial rápida do código -. Vou lê-lo mais e ver o que mais eu posso encontrar

EDIT:

Siga o meu conselho originais acima, e simplificar o seu método principal para a seguinte aparência e seu paintComponent () será chamado:

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

Também gostaria de ler sobre usando timers para executar animação, bem como despacho geral evento Swing e como / quando para substituir vários métodos de pintura.

http://java.sun.com/products/jfc/ tsc / artigos / pintura /

http://java.sun.com/docs /books/tutorial/uiswing/misc/timer.html

http://java.sun.com/docs /books/tutorial/uiswing/concurrency/dispatch.html

Para tornar Tom Hawtin - tackline feliz. Eu reescrevi mais uma vez

Existem várias coisas que mudaram (confira as linhas com o comentário //new)

reescreveu completamente

  • Split em um novo arquivo limpo componente (ImageLoadTest.java) e um arquivo de testá-lo (Tester.java)

As melhorias no código de cartazes originais

  • construtor chamado do pai no construtor ImageLoadTest (super())
  • segundo construtor fornecido a lista conjunto de imagens que o componente deve exibir
  • IMPORTANTE: chamada para setPreferredSize() do componente no construtor. Se o tamanho não for definido balanço é claro que não vai pintar o seu componente. tamanho preferida é baseada na máxima. largura de todas as imagens e na soma de todas as alturas imagem
  • chamada para super.paintComponent(g) em anulado paintComponent()
  • mudou paintComponent para yOffset automaticamente base sobre a altura de imagens sendo desenhado

  • GUI inicialização feito em EDT

  • como código original baseado no uso de sleep() para ilustrar o carregamento e carregamento de imagens poderia levar um longo tempo de SwingWorker são usados ??
  • worker esperas em seguida, define novo título e, em seguida, carrega imagens
  • na conclusão do worker em done() finalmente adiciona o componente ao JFrame ea exibe. Adicionado componente para o painel de conteúdo de JFrame conforme descrito no JFrame api . E, como descrito no javadoc fez chamada necessário validate() em JFrame depois de chamar add(), como o JFrame é um recipiente já visível whichs crianças alterado.

javdoc citação de validate()

O método de validação é usado para causar uma recipiente para colocar para fora seus subcomponentes novamente. Deve ser invocado quando este subcomponentes do recipiente são modificados (Adicionado a ou removido do recipiente, ou disposição relacionada informações trocadas) após a contêiner foi exibido.

  • segundo trabalhador só faz um pouco mais de espera, em seguida, define a cor de fundo para preto
  • JPanel usado como baseclass para ImageLoadTest para correção setBackground() que eu não poderia começar a trabalhar com JComponent.

Assim, seus principais problemas onde que você não definir o tamanho preferido do componente e que você não ligou validate() na JFrame depois de adicionar algo para o recipiente já visível.

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

Eu recomendo a leitura do primeiro par de capítulos de "Filthy Rich Clients". Eu tinha sido usando Swing há anos, mas só depois de ler este livro que eu finalmente compreender exatamente como pintura obras mecanismo de Java.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top