8

在过去的两天里,我试图了解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

5 回答 5

11

原始代码中未调用paintComponent() 的原因之一是该组件具有“零大小”,并且RepaintManger 足够聪明,不会尝试绘制没有大小的东西。

代码重新排序起作用的原因是,当您将组件添加到框架然后使框架可见时,会调用布局管理器来布局组件。默认情况下,框架使用 BorderLayout 并且默认情况下将组件添加到 BorderLayout 的中心,这恰好为组件提供了所有可用空间,因此它被绘制。

但是,您将内容窗格的布局管理器更改为 FlowLayout,您仍然会遇到问题,因为 FlowLayout 尊重组件的首选大小为零。

所以你真正需要做的是为你的组件分配一个首选大小,这样布局管理器就可以完成他们的工作。

于 2009-11-04T21:21:06.290 回答
5

这里的一个主要问题是您没有在Event Dispatch Thread (EDT)上更新您的 swing 组件。尝试将所有代码包装在 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

于 2009-11-04T20:06:07.097 回答
4

为了让Tom Hawtin - tackline高兴。我又重写了一遍

我改变了几件事(检查//new评论的行)

完全重写了

  • 拆分成一个干净的新组件文件 ( ImageLoadTest.java) 和一个测试它的文件 ( Tester.java)

原始海报代码的改进

  • ImageLoadTest在构造函数(super())中调用父级的构造函数
  • 提供了第二个构造函数来设置组件应该显示的图像列表
  • 重要提示:在构造函数中调用setPreferredSize()组件。如果未设置 size,swing 当然不会绘制您的组件。首选尺寸基于最大值。所有图像的宽度和所有图像高度的总和
  • 调用super.paintComponent(g)in 覆盖paintComponent()
  • 更改paintComponent为自动基于yOffset正在绘制的图像的高度

  • 在 EDT 上完成 GUI 初始化

  • 因为基于 using 的原始代码来说明图像的加载和加载可能需要很sleep()长时间SwingWorker
  • worker等待然后设置新标题然后加载图像
  • 完成后,workerindone()最终将组件添加到JFrame并显示它。将组件添加到JFrame apiJFrame中所述的内容窗格。并且如 javadoc 中所述,在调用后对on进行了必要的调用,因为它是一个已经可见的容器,其子项已更改。validate()JFrameadd()JFrame

javdoc 引用来自validate()

validate 方法用于使容器重新布置其子组件。在容器显示后修改此容器的子组件(添加到容器或从容器中删除,或更改布局相关信息)时,应调用它。

  • 第二个工人只是做了一些等待然后将背景颜色设置为黑色
  • 用作修复我无法使用JPanel的基类。ImageLoadTestsetBackground()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);
    }
  }
}

测试者.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;
  }
}
于 2009-11-04T21:41:30.697 回答
3

这些是导致其无法工作的原始代码的主要问题:

  1. 在 add() 操作后不调用 validate()
  2. 不设置组件的首选大小。
  3. 覆盖时不调用 super.paintComponent() (这使得 setBackground() 调用不起作用)
  4. 我需要从 JPanel 继承才能对其进行绘制。Component 和 JComponent 都不足以使 setBackground() 调用起作用,即使在修复点 3 时也是如此。

完成上述操作后,调用方法paintComponent或paint真的没关系,只要我记得在开始时调用超级构造函数,两者似乎都可以工作。

这些信息是从@jitter、@tackline 和@camickr 所写的内容中收集的,非常荣幸!

PS不知道回答你自己的问题是否被认为是不好的形式,但由于我需要的信息是从几个答案中组合而成的,我认为最好的方法是升级其他答案并像这样写一个总结。

于 2009-11-05T07:04:55.687 回答
2

我推荐阅读“肮脏的富客户”的前几章。多年来我一直在使用 Swing,但直到读了这本书后,我才终于完全理解了 Java 的绘画机制是如何工作的。

于 2009-11-04T20:22:32.227 回答