どんな時にも今までに描いた絵を消すことなく一度見えなくなったウインドウが戻ることなどでも絵が消えないようにするには、絵をJPanelではなく別のところに描いておいて、paintComponentはその絵をJPanelにコピーするだけにします。
まず別の場所の用意ですが、
BufferedImage buffimg = new BufferedImage(int width,int height, int TYPE); Graphics bfg = buffimg.createGraphics();
width, height はピクセル数、TYPEはカラーなのかグレースケールか透明度(アルファチャンネル)を持つのかの指定です。
このBufferedImageのインスタンスに createGraphics() メソッドで Graphics クラスのインスタンスを割り当てておきます。
こうしておくと、後で
bfg.fillOval(x,y,w,h);
などと描画ができます。
paintComponentには
myg.drawImage(buffimg, 0, 0, this);
と書いて、bufimg内のデータをJPanel内にコピーします。
import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.awt.image.BufferedImage; public class EventRandom2 extends JFrame implements ActionListener{ JButton mybtn; MyPanel mypnl; int pcct=0; //paintComponentが呼び出された回数 int clct=0; //クリックした回数 public EventRandom2() { setDefaultCloseOperation(EXIT_ON_CLOSE); setTitle("イベントで変化する模様"); setLayout(new BorderLayout()); mypnl = new MyPanel(400,300); mybtn = new JButton("draw"); add(mypnl, BorderLayout.CENTER); add(mybtn,BorderLayout.SOUTH); mybtn.addActionListener(this); //mypnl.setBackground(new Color(255,255,191)); //mypnl.setPreferredSize(new Dimension(400,300)); pack(); setVisible(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == mybtn) { clct++; mypnl.drawToBuff(); //mypnl.repaint(); } } public static void main(String[] args){ EventRandom2 myframe = new EventRandom2(); } public class MyPanel extends JPanel{ BufferedImage buffimg; Graphics bfg; Color bgcolor = new Color(255,255,191); public MyPanel(int width, int height){ setPreferredSize(new Dimension(width,height)); buffimg = new BufferedImage( width,height,BufferedImage.TYPE_INT_RGB); bfg = buffimg.createGraphics(); bfg.setColor(bgcolor); bfg.fillRect(0, 0, width, height); } @Override public void paintComponent(Graphics myg){ //super.paintComponent(myg); pcct++; myg.drawImage(buffimg, 0, 0, this); } public void drawToBuff(){ for(int i=0; 10>i; i++){ Color rcolor = randomColor(); bfg.setColor(rcolor); int x = (int)(400*Math.random()); int y = (int)(300*Math.random()); int h = (int)(50*Math.random()+5); bfg.fillOval(x-h/2,y-h/2,h,h); } bfg.setColor(bgcolor); bfg.fillRect(10,4,60,20); bfg.setColor(Color.black); bfg.drawString(pcct+" "+clct,10,20); repaint(); } public Color randomColor(){ int r=0; int g=0; int b=0; int dc = 256; g = (int)(dc*Math.random()); if (g > 255){ g = 0; } Color c = new Color(r,g,b); return c; } } }
これで、ウインドウを最小化して戻したり、ウインドウの大きさを変える場合も描きためた円はなくなりません。
でも絵の出ない部分ができてしまいます。これをどうするかは次の改良点です。
上記プログラムをつくって動作を確認しなさい。
Javaの 「API 仕様書」 に14種類のTYPEがフィールドとして定義されているがその違いの説明は十分でない。
たとえば透明度を持たないカラーでも、
の3つ
たとえば透明度を持つカラーでは、
の4つがあります。
javaではintは4バイトなので、能力的には同等。データの構造が異なるだけなのでしょう。どうせGraphicsのメソッドで描画しますから、24ビットカラーなら上記3つのうちのどれか、アルファチャンネルを持つ24ビットカラーなら上記4つのうちのどれかで構わないのではないかと思われます。
インデックスカラーとかグレースケールを使おうとするならまた別の話です。
今回はJPanel(を継承したクラスのインスタンス)に描画される画像を保持するという流れなのでBufferedImageはJPanelが持つことにしました。JPanel(を継承したクラスのインスタンス)に対してランダムな円の追加を指示すると黙ってBufferedImageに描画します。その指示はmypnl.drawToBuff();ですが、drawToBuff()という名前がバッファを使うという雰囲気を示しますが、完全にmypnlまかせに出来ます。Mypanelの外ではBufferedImageのことを考える必要がありません。
ただこの方法でBufferedImageのサイズをMypanelから取得しようとすると困難に出会います。
今回コンストラクタで作っていますが、new MyPanel() の時点ではMyPanelのインスタンスのサイズは決まっていませんのであらかじめ400,300と大きさを指示する必要があります。
ユーザーがウィンドウを拡大してから描画を始めたらそれに合わせたサイズのBufferedImageを作るという場合には、コンストラクタではなく、初めて drawToBuff() メソッドが呼ばれたときに作るしかないでしょう。 drawToBuff()が呼ばれるときはボタンが表示されていますから、パネルも表示されていて大きさが決まっています。パネルの大きさを取得するメソッドがありますからそれに合わせてBufferedImageを作るという算段になります。
興味の対象がパネルではなく、イメージそのものの場合は真っ先にBufferedImageを作っておくことも考えられます。大きなサイズにしておいて、印刷したりファイルに保存したりするのが主体ならば、パネルにBufferedImageを付属させるのは自然ではないでしょう。
なぜ buffimg.fillRect(x, y, width, height) でないのか
createGraphics()は何度やっても画像は壊れない。
Graphics を拡張した Graphics2D クラスは混用しても大丈夫?