8

過去2日間、私はJavaがグラフィックスを処理する方法を理解しようとしましたが、それだけで惨めに失敗しました。私の主な問題は、paint()(または新しいpaintComponent())がいつどのように呼び出されるかを正確に理解することです。

物事がいつ作成されるかを確認するために作成した次のコードでは、自分で手動で呼び出しを追加するか、JFrame.paintAll()/ JFrame.paintComponents()を呼び出さない限り、paintComponent()が呼び出されることはありません。

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

5

ここでの大きな問題の 1 つは、イベント ディスパッチ スレッド (EDT)でスイング コンポーネントを更新していないことです。次のように、メイン メソッドのすべてのコードをラップしてみてください。

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

また、フレームを表示する前に ImageLoadTest をフレームに追加します。これは、コードをざっと読んだことに基づいています。さらに読んで、他に何が見つかるか見てみます。

編集:

上記の最初のアドバイスに従い、main メソッドを次のように単純化すると、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 - タックラインを幸せにするために。もう一度書き直しました

変更したことがいくつかあります(//newコメントのある行を確認してください)

完全に書き直しました

  • ImageLoadTest.javaクリーンな新しいコンポーネント ファイル ( ) とそれをテストするファイル ( Tester.java)に分割します。

元のポスター コードの改善

  • コンストラクターで親のコンストラクターを呼び出すImageLoadTest( super())
  • コンポーネントが表示する画像のリストを設定するための 2 番目のコンストラクターを提供
  • 重要:setPreferredSize()コンストラクターでのコンポーネントの呼び出し。もちろん、サイズが設定されていない場合、スイングはコンポーネントをペイントしません。好ましいサイズは最大に基づいています。すべての画像の幅とすべての画像の高さの合計
  • super.paintComponent(g)オーバーライドでの呼び出しpaintComponent()
  • 描画されている画像の高さにpaintComponent基づいて自動的に変更されましたyOffset

  • EDT で GUI の初期化を行う

  • 画像の読み込みと読み込みを説明するために使用する元のコードsleep()は時間がかかる可能性があるため、SwingWorker使用される
  • worker待ってから新しいタイトルを設定し、画像をロードします
  • 完了すると、 workerin はdone()最終的にコンポーネントを に追加してJFrame表示します。JFrame APIJFrameで説明されているように、コンテンツ ペインにコンポーネントを追加しました。そして、 javadoc で説明されているように、 を呼び出した後に必要な呼び出しを行いました。これは、子が変更された既に表示されているコンテナーであるためです。validate()JFrameadd()JFrame

からのjavdocの引用validate()

検証メソッドは、コンテナにそのサブコンポーネントを再度レイアウトさせるために使用されます。コンテナーが表示された後に、このコンテナーのサブコンポーネントが変更された (コンテナーに追加またはコンテナーから削除された、またはレイアウト関連の情報が変更された) 場合に呼び出す必要があります。

  • 2 番目のワーカーはもう少し待機してから、背景色を黒に設定します
  • で作業できなかったものを修正する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);
    }
  }
}

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;
  }
}
于 2009-11-04T21:41:30.697 に答える
3

元のコードが機能しない原因となった主な問題は次のとおりです。

  1. add() 操作の後に validate() を呼び出さない
  2. コンポーネントの推奨サイズを設定していません。
  3. オーバーライド時に super.paintComponent() を呼び出さない (これにより setBackground() 呼び出しが機能しなくなりました)
  4. ペイントするには、JPanel から継承する必要がありました。ポイント 3 を修正しても、Component も JComponent も setBackground() 呼び出しが機能するのに十分ではありませんでした。

上記を行った後、最初にスーパーコンストラクターを呼び出すことを覚えていれば、メソッド paintComponent または paint を呼び出すかどうかは問題ではありませんでした。

この情報は、@jitter、@tackline、および @camickr が書いたものから集められたものです。

PS自分の質問に答えることが悪い形と見なされるかどうかはわかりませんが、必要な情報はいくつかの回答から集められたので、他の回答を更新してこのように要約するのが最善の方法だと思いました.

于 2009-11-05T07:04:55.687 に答える
2

「Filthy Rich Clients」の最初の数章を読むことをお勧めします。私は何年もの間 Swing を使用していましたが、この本を読んで初めて、Java の描画メカニズムがどのように機能するかを完全に理解することができました。

于 2009-11-04T20:22:32.227 に答える