代码之家  ›  专栏  ›  技术社区  ›  oligofren

为什么从未调用paint()/paintcomponent()?

  •  8
  • oligofren  · 技术社区  · 15 年前

    在过去的两天里,我一直试图 理解 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;
     }
    }
    
    5 回复  |  直到 11 年前
        1
  •  11
  •   camickr    15 年前

    在原始代码中没有调用paint component()的原因之一是,该组件的大小为“零”,而repaintmanager足够聪明,不尝试绘制没有大小的内容。

    重新排序代码的原因是,当您将组件添加到框架,然后使框架可见时,将调用布局管理器来布局组件。默认情况下,框架使用borderlayout,并且默认情况下,将组件添加到borderlayout的中心,这样会为组件提供所有可用空间,以便对其进行绘制。

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

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

        2
  •  4
  •   jitter    15 年前

    这里的一个主要问题是,您没有在 Event Dispatch Thread (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

        3
  •  4
  •   jitter    15 年前

    使 汤姆·霍丁-铲球 快乐。我又重写了一遍

    有几件事我改变了(用 //new 注释)

    完全重写

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

    原海报代码的改进

    • 在中调用父级的构造函数 ImageLoadTest 构造函数(构造器) super() )
    • 提供第二个构造函数来设置组件应显示的图像列表
    • 重要事项:呼叫 setPreferredSize() 构造函数中的组件。如果没有设置尺寸,Swing当然不会绘制组件。首选大小基于所有图像的最大宽度和所有图像高度的总和。
    • 打电话给 super.paintComponent(g) 在重写中 paintComponent()
    • 改变 paintComponent 自动建立基础 yOffset 关于正在绘制的图像的高度

    • 在EDT上完成GUI初始化

    • 基于使用的原始代码 sleep() 为了说明图像的加载和加载可能需要很长时间 SwingWorker 使用的是
    • worker 等待,然后设置新标题,然后加载图像
    • 完成时 工人 在里面 done() 最后将组件添加到 JFrame 并显示出来。已将组件添加到的内容窗格 窗口 如上所述 JFrame 应用程序编程接口。正如JavaDoc中所描述的那样,向 validate() 窗口 打电话后 add() ,作为 窗口 是一个已可见的容器,子容器已更改。

    JavDoc引文来自 有效()

    validate方法用于 用于布局其子组件的容器 再一次。它应该在以下情况下调用 修改容器的子组件 (添加到或从 容器或布局相关 信息更改)之后 已显示容器。

    • 第二个工人只是做了更多的等待,然后将背景色设置为黑色。
    • 习惯于 JPanel 作为的基类 映像测试 修理 setBackground() 我不能和他一起工作 JComponent .

    所以你的主要问题是,你没有设置组件的首选大小,也没有调用 有效() 窗口 在向已经可见的容器中添加一些内容之后。

    这应该管用

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

    爪哇

    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;
      }
    }
    
        4
  •  3
  •   oligofren    11 年前

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

    1. 在执行add()操作后不调用validate()。
    2. 未设置组件的首选大小。
    3. 在重写super.paintcomponent()时不调用它(这使得 background()调用不工作)
    4. 我需要继承JPanel的作品才能让它上漆。组件和jcomponent都不足以使background()调用工作,即使在修复点3时也是如此。

    做了以上的工作之后,调用方法paintcomponent或paint真的没关系,只要我记得在开始时调用超级构造函数,这两个方法似乎都可以工作。

    这个信息是由@jitter、@tockline和@camicker写的,真是太棒了!

    P.S.不知道回答你自己的问题是否被认为是不好的形式,但是由于我需要的信息是从几个答案中收集的,所以我认为最好的方法是修改其他答案,然后写一个这样的总结。

        5
  •  2
  •   Steve McLeod    15 年前

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