0

For what I understand Swing will decide when things need to be repainted, that would explain why paintComponent() is executing twice. But I've made an application that sleeps 16ms, repaints, sleeps 16 ms, repaints, sleeps 16 ms, and so on:

while(true)
{
    frame.repaint();
    try{Thread.sleep(16)}catch(Exception e){}
}

It should work at 60fps. However, FPS measuring programs (like FRAPS) show the application runs at 120fps. So basically, what the application is doing is: draw frame, draw frame, sleep, draw frame, draw frame, sleep... How can I tell swing to draw one frame for each repaint() call? (Oh and I've tried using a Timer instead of sleep(), and the result is the same).

Here's for example the SwingPaintDemo found at an Oracle tutorial. I've added a while loop that will repaint every 16ms. I've also set undecorated to true (it's the only way FRAPS will show me the actual number of frames per second).

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/*
 ***************************************************************
 * Silly Sample program which demonstrates the basic paint
 * mechanism for Swing components.
 ***************************************************************
 */
public class SwingPaintDemo {

    public static void main(String[] args) {
        JFrame f = new JFrame("Aim For the Center");
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        Container panel = new BullsEyePanel();
        panel.add(new JLabel("BullsEye!", SwingConstants.CENTER), BorderLayout.CENTER);
        f.setUndecorated(true);
        f.setSize(200, 200);
        f.getContentPane().add(panel, BorderLayout.CENTER);
        f.show();

        while(true)
        {
            f.repaint();
            try{Thread.sleep(16);}catch(Exception e){}
        }
    }
}

/**
 * A Swing container that renders a bullseye background
 * where the area around the bullseye is transparent.
 */
class BullsEyePanel extends JPanel {

    public BullsEyePanel() {
        super();
        setOpaque(false); // we don't paint all our bits
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createLineBorder(Color.black));
    }

    public Dimension getPreferredSize() {
        // Figure out what the layout manager needs and
        // then add 100 to the largest of the dimensions
        // in order to enforce a 'round' bullseye 
        Dimension layoutSize = super.getPreferredSize();
        int max = Math.max(layoutSize.width,layoutSize.height);
        return new Dimension(max+100,max+100);
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Dimension size = getSize();
        int x = 0;
        int y = 0;
        int i = 0;
        while(x < size.width && y < size.height) {
            g.setColor(i%2==0? Color.red : Color.white);
            g.fillOval(x,y,size.width-(2*x),size.height-(2*y));
            x+=10; y+=10; i++;
        }
    }
}
user1319734
  • 427
  • 1
  • 4
  • 7
  • For better help sooner, please include an [sscce](http://www.sscce.org) demonstrating said problem. – user1329572 May 07 '12 at 12:15
  • 2
    Exact duplicate of [Set program to 60FPS, but runs at 120FPS?](http://stackoverflow.com/questions/10473637/set-program-to-60fps-but-runs-at-120fps). ***Don't ask the same question twice!*** – Andrew Thompson May 07 '12 at 12:18
  • Haha I know it's kinda the same, but in that question I used the Timer as you said to set the FPS at 60, but it looks as if it were 30, since it paints each frame twice. So the problem is still there, and the Timer is not the solution. Now I want to know how to set paintComponent to paint just once and not twice. – user1319734 May 07 '12 at 12:21
  • Then you should edit that question, and let interested parties know of the update by a comment. *"the Timer is not the solution."* Certainly not when you throw in calls to `Thread.sleep(n)`. Don't do that! But like I said, this is not the question for it. The earlier question was. – Andrew Thompson May 07 '12 at 12:31
  • Ok, I'll edit the other question and delete this one later. – user1319734 May 07 '12 at 12:32

1 Answers1

3

1) basically methods inside paintComponent() doesn't required to call for repaint()

2) Swing GUI invoke paintComponent() by itself when is necessary to repaint

3) for todays Swing GUI with Graphics(2D) use Swing Timer by default

4) code for frame.repaint(); invoke duplicated repaint, remove that

5) frame.repaint(); is usefull method if you add / remove / modify already visible JFrame

6) did you mean

enter image description hereenter image description here

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SwingPaintDemo {

    public static void main(String[] args) {
        JFrame f = new JFrame("Aim For the Center");
        f.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        Container panel = new BullsEyePanel();
        panel.add(new JLabel("BullsEye!", SwingConstants.CENTER), BorderLayout.CENTER);
        f.getContentPane().add(panel, BorderLayout.CENTER);
        f.pack();
        f.setVisible(true);
    }
}

/**
 * A Swing container that renders a bullseye background
 * where the area around the bullseye is transparent.
 */
class BullsEyePanel extends JPanel {

    public BullsEyePanel() {
        super();
        setOpaque(false); // we don't paint all our bits
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createLineBorder(Color.black));
    }

    @Override
    public Dimension getPreferredSize() {
        // Figure out what the layout manager needs and
        // then add 100 to the largest of the dimensions
        // in order to enforce a 'round' bullseye
        Dimension layoutSize = super.getPreferredSize();
        int max = Math.max(layoutSize.width, layoutSize.height);
        return new Dimension(max + 100, max + 100);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Dimension size = getSize();
        int x = 0;
        int y = 0;
        int i = 0;
        while (x < size.width && y < size.height) {
            g.setColor(i % 2 == 0 ? Color.red : Color.white);
            g.fillOval(x, y, size.width - (2 * x), size.height - (2 * y));
            x += 10;
            y += 10;
            i++;
        }
    }
}

EDIT, what's wrong with @trashgod code linked in your previous question, there is about JPanel's repaint (this.repaint())

enter image description here

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;

/** @see http://stackoverflow.com/questions/3256941 */
public class AnimationTest extends JPanel implements ActionListener {

    private static final int WIDE = 640;
    private static final int HIGH = 480;
    private static final int RADIUS = 25;
    private static final int FRAMES = 24;
    private static final long serialVersionUID = 1L;
    private final Timer timer = new Timer(20, this);
    private final Rectangle rect = new Rectangle();
    private BufferedImage background;
    private int index;
    private long totalTime;
    private long averageTime;
    private int frameCount;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new AnimationTest().create();
            }
        });
    }

    private void create() {
        JFrame f = new JFrame("AnimationTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        timer.start();
    }

    public AnimationTest() {
        super(true);
        this.setOpaque(false);
        this.setPreferredSize(new Dimension(WIDE, HIGH));
        this.addMouseListener(new MouseHandler());
        this.addComponentListener(new ComponentHandler());
    }

    @Override
    protected void paintComponent(Graphics g) {
        long start = System.nanoTime();
        super.paintComponent(g);
        int w = this.getWidth();
        int h = this.getHeight();
        g.drawImage(background, 0, 0, this);
        double theta = 2 * Math.PI * index++ / 64;
        g.setColor(Color.blue);
        rect.setRect((int) (Math.sin(theta) * w / 3 + w / 2 - RADIUS),
                (int) (Math.cos(theta) * h / 3 + h / 2 - RADIUS), 2 * RADIUS, 2 * RADIUS);
        g.fillOval(rect.x, rect.y, rect.width, rect.height);
        g.setColor(Color.white);
        if (frameCount == FRAMES) {
            averageTime = totalTime / FRAMES;
            totalTime = 0;
            frameCount = 0;
        } else {
            totalTime += System.nanoTime() - start;
            frameCount++;
        }
        String s = String.format("%1$5.3f", averageTime / 1000000d);
        g.drawString(s, 5, 16);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        this.repaint();
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            super.mousePressed(e);
            JTextField field = new JTextField("test");
            Dimension d = field.getPreferredSize();
            field.setBounds(e.getX(), e.getY(), d.width, d.height);
            add(field);
        }
    }

    private class ComponentHandler extends ComponentAdapter {

        private final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        private final GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
        private final Random r = new Random();

        @Override
        public void componentResized(ComponentEvent e) {
            super.componentResized(e);
            int w = getWidth();
            int h = getHeight();
            background = gc.createCompatibleImage(w, h, Transparency.OPAQUE);
            Graphics2D g = background.createGraphics();
            g.clearRect(0, 0, w, h);
            g.setColor(Color.green.darker());
            for (int i = 0; i < 128; i++) {
                g.drawLine(w / 2, h / 2, r.nextInt(w), r.nextInt(h));
            }
            g.dispose();
            System.out.println("Resized to " + w + " x " + h);
        }
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • This program draws a still image, so actually it's not necessary to repaint, but I just put it as an example. The application I'm doing needs to repaint since objects move. And in the application I posted here it still paints the same still image twice. – user1319734 May 07 '12 at 12:47
  • @ControlAltDel I think that this is made by tjacobs too, didn't it – mKorbel May 07 '12 at 12:47
  • The application with the blue ball works at 60fps. I'm gonna take a look at it. Thanks for posting it. – user1319734 May 07 '12 at 13:45
  • @user1319734: [`AnimationTest `](http://stackoverflow.com/a/3256941/230513) uses a period of 20 ms, which corresponds to a frequency of 1000/20 = 50 Hz. – trashgod May 07 '12 at 16:09
  • @trashgod basically this is reason why I'm copy code alyways, for example in some Laptops (old screens) you can't see difference betweens basic narratives and links, anyway great idea this code :-). thank you for that – mKorbel May 07 '12 at 16:18
  • @mKorbel: androids hide links that way, too. user1319734: At 60 Hz, you have ~16.6 ms for painting. Note how the rate changes as the frame is resized; `repaint()` gets called automatically. – trashgod May 07 '12 at 16:32