3

Swing でグラフィック処理 (線の描画など) を行うコツをつかもうとしています。これまでのところ、私が見たすべてのチュートリアルは をオーバーライドするクラスを宣言し、paintComponentすべてのpaintComponentメソッドは、赤い四角形を描画するなど、特定の設定を行います (ただし、毎回異なる場所に描画する可能性があります)。または、多数の線や図形を描画する場合でも、このpaintComponentメソッドはすべてを一度に実行します。

私は理解しようとしています: コンポーネントにあるものを描画し、後で以前に描画したものを消去せずに、その上に別のものを描画したいとします。私が最初に考えたのは、 paintComponentオーバーライドにコールバックを呼び出させることでした。

import java.awt.*;
import javax.swing.*;
public class DrawTest {

    private interface GraphicsAction {
        public void action (Graphics g);
    }

    private static class TestPanel extends JPanel {

        private GraphicsAction paintAction;

        public void draw (GraphicsAction action) {
            paintAction = action;
            repaint();
        }

        @Override
        public void paintComponent (Graphics g) {
            super.paintComponent (g);
            if (paintAction != null)
                paintAction.action(g);
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame ("DrawTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(500,500));

        TestPanel p = new TestPanel ();
        frame.getContentPane().add(p);
        frame.pack();
        frame.setVisible(true);
        p.repaint();

        p.draw (new GraphicsAction () {
            public void action (Graphics g) {
                g.setColor(Color.RED);
                g.drawLine(5, 30, 100, 50);
            }
        });

        // in real life, we would do some other stuff and then
        // later something would want to add a blue line to the
        // diagram 

        p.draw (new GraphicsAction () {
            public void action (Graphics g) {
                g.setColor(Color.BLUE);
                g.drawLine(5, 30, 150, 40);
            }
        });

    }

    public static void main (String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGui();                 
            }
        });
    }
}

これはうまくいきません。青い線は表示されますが、赤い線は表示されません。これは、青い線を引くとrepaint()indrawによってすべてが最初からやり直されるためだと思いますが、よくわかりません。paintComponentとにかく、私は他にどのように呼び出されるのかわかりません 。

また、Thread.sleep(1000)2 つの呼び出しの間に a を挿入するp.drawと、赤い線が 1 秒も表示されません。そのため、必要なときにグラフィックを表示する方法がまったくわかりません。

Swing で「インクリメンタル グラフィックス」を検索しましたが、解決策を見つけるのに役立つものは何もありません。インクリメンタル グラフィックスを実現するためのメソッドのオーバーライドについて説明している Oracle の記事「Painting in AWT and Swing」をupdate() 見つけましたが、これが実際に行われた例は見つかりませんでした。

では、どうすればこれをやりたいことができるのでしょうか? それを行う簡単な方法があるべきであるほど一般的なタスクのように思えますが、私はそれを見つけていません。を呼び出さなくても実行できるはずだと思いますが getGraphics、これは、他の StackOverflow 応答に基づいて、せいぜい一種のゴーシュになります。

4

1 に答える 1

6

Swing でのペイントは破壊的です。つまり、新しいペイント サイクルが実行されるたびに、ペイントしているオブジェクトの状態に従って出力を完全に再構築する必要があります。

AWT と Swingでのペイントをご覧ください。

だからあなたが電話するとき

p.draw (new GraphicsAction () {
    public void action (Graphics g) {
        g.setColor(Color.RED);
        g.drawLine(5, 30, 100, 50);
    }
});

に続く

p.draw (new GraphicsAction () {
    public void action (Graphics g) {
        g.setColor(Color.BLUE);
        g.drawLine(5, 30, 150, 40);
    }
});

あなたは基本的に最初の行動を捨てています。現時点で再描画がどのようにスケジュールされているかは無視します。最初のリクエストは「赤い線を描く」、2 番目のリクエストは「青い線を描く」ですが、これらのアクションが実行される前にGraphicsコンテキストが消去され、更新の準備が整います。

Graphics提供されるコンテキストは共有リソースであるため、これは非常に重要です。前にペイントされたすべてのコンポーネントは同じコンテキストを使用しており、後でペイントされたすべてのコンポーネントは同じコンテキストを使用します。つまり、ペイントする前にコンテキストを「クリーン」にしないと、不要なペイント アーティファクトが発生する可能性があります。

しかし、どうすればそれを回避できますか??

ここにはいくつかの選択肢があります。

BufferedImage独自のコンテキストを持つバッキング バッファー (または ) に描画できGraphicsます。これは、追加でき、メソッドで「ペイント」するだけで済みますpaintComponent

これは、 を呼び出すたびにp.draw(...)、実際には最初にこのバッファにペイントしてから を呼び出すことを意味しますrepaint

これに関する問題は、バッファのサイズを維持する必要があることです。コンポーネントのサイズが変更されるたびに、コンポーネントの新しいサイズに基づいて、このバッファーを新しいバッファーにコピーする必要があります。これは少し厄介ですが、実行可能です。

もう 1 つの解決策は、各アクションを に配置しList、必要に応じて をループして、必要にList応じてアクションを再適用することです。

これはおそらく最も単純な方法ですが、アクションの数が増えると、ペイント プロセスの効率が低下し、ペイント プロセスが遅くなる可能性があります。

2 つを組み合わせて使用​​することもできます。バッファが存在しない場合はバッファを生成し、アクションをループしてそれらをバッファにレンダリングし、メソッドListでバッファを単純にペイントします。paintComponentコンポーネントのサイズが変更されるたびに、単純nullにバッファpaintComponentを再生成できるようにします...たとえば...

また、2 つの p.draw 呼び出しの間に Thread.sleep(1000) を配置すると、

Swing はシングルスレッドのフレームワークです。つまり、すべての更新と変更は、イベント ディスパッチ スレッドのコンテキスト内で行われることが期待されます。

同様に、EDT の実行をブロックするものはすべて、(とりわけ) ペイント要求の処理を妨げます。

これはsleep、呼び出しの間にp.draw、EDT の実行を停止していることを意味します。つまり、ペイント要求を処理できません...

詳細については、Swing での同時実行をご覧ください。

例で更新

ここに画像の説明を入力

ただ指摘したいのは、それは本当に非効率的だということです。が呼び出されるたびにバッファを再作成すると、存続期間invalidateの短いオブジェクトが多数作成され、パフォーマンスが大幅に低下する可能性があります。

通常、が呼び出されるjavax.swing.Timerたびに再起動される、非繰り返しに設定された を使用します。invalidateこれは、短い遅延 (125 ~ 250 ミリ秒の間) に設定されます。タイマーがトリガーされると、この時点で単純にバッファーを再構築しますが、これは単なる例です ;)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class DrawTest {

    private interface GraphicsAction {

        public void action(Graphics g);
    }

    private static class TestPanel extends JPanel {

        private GraphicsAction paintAction;
        private BufferedImage buffer;

        @Override
        public void invalidate() {
            BufferedImage img = new BufferedImage(
                    Math.max(1, getWidth()),
                    Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = img.createGraphics();
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            if (buffer != null) {
                g2d.drawImage(buffer, 0, 0, this);
            }
            g2d.dispose();
            buffer = img;
            super.invalidate();
        }

        protected BufferedImage getBuffer() {
            if (buffer == null) {
                buffer = new BufferedImage(
                        Math.max(1, getWidth()),
                        Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = buffer.createGraphics();
                g2d.setColor(getBackground());
                g2d.fillRect(0, 0, getWidth(), getHeight());
                g2d.dispose();
            }
            return buffer;
        }

        public void draw(GraphicsAction action) {
            BufferedImage buffer = getBuffer();
            Graphics2D g2d = buffer.createGraphics();
            action.action(g2d);
            g2d.dispose();
            repaint();
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(getBuffer(), 0, 0, this);
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("DrawTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(500, 500));

        TestPanel p = new TestPanel();
        frame.getContentPane().add(p);
        frame.pack();
        frame.setVisible(true);
        p.repaint();

        p.draw(new GraphicsAction() {
            public void action(Graphics g) {
                g.setColor(Color.RED);
                g.drawLine(5, 30, 100, 50);
            }
        });

        // in real life, we would do some other stuff and then
        // later something would want to add a blue line to the
        // diagram 
        p.draw(new GraphicsAction() {
            public void action(Graphics g) {
                g.setColor(Color.BLUE);
                g.drawLine(5, 30, 150, 40);
            }
        });

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGui();
            }
        });
    }
}
于 2013-09-17T01:36:05.407 に答える