0

I have a JFrame to which I add a JPanel. I'm doing some animation, so I implement a BufferStrategy for rendering. I also a rendering loop to keep it rendering while running.

If I run the program like normal, the JPanel renders correctly. Of course, then there's no animation. If I run it with the loop and hte BufferedStrategy, the JPanel extends to the full size of the application, and underneath the title bar of the JFrame. I can't find a good reason for this to be happening, but it's frustrating because I need to do some precise drawing, and can't have some of it hidden underneath the title bar.

I assume it's because I'm not calling super.paintComponent(), but I really shouldn't call it anyway, since I'm rendering on my own, not within the normal Swing pipeline.

Is there some API call I need to make to make the JPanel position itself correctly within my render call?

import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;

public class MainFrame extends JFrame implements Runnable {

    private static final long serialVersionUID = 2190062312369662956L;

    protected ViewPanel _viewPanel = null;

    public MainFrame() {
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

        createGui();
    }

    protected void createGui() {

        setSize( 600, 400 );
        setTitle( "Exact Positioning" );
        setVisible( true );
        setResizable( false );

        _viewPanel = new ViewPanel();
        _viewPanel.init();

        // the layout type shouldn't matter since this is the only component in the frame
        add( _viewPanel );
    }

    @Override
    public void run() {

        // setup
        this.createBufferStrategy( 2 );
        BufferStrategy buffStrategy = this.getBufferStrategy();

        // render loop
        while( true ) {

            Graphics g = null;
            try {
                g = buffStrategy.getDrawGraphics();
                _viewPanel.render( g );
            } finally {
                g.dispose();
            }
            buffStrategy.show();

            // pause a tad
            try {
                Thread.sleep( 500 );
            } catch (InterruptedException e) {
                // Required catch block
                e.printStackTrace();
            } catch (Exception e) {
                System.out.println( "Sorry, don't know what happened: " + e.toString() );
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {

        Thread t1 = new Thread(new MainFrame()); 
        t1.start();
        // if I start the app this way, the JPanel draws correctly
//      MainFrame a = new MainFrame(); 
    }
}

The JPanel:

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

import javax.swing.JPanel;

public class ViewPanel extends JPanel {

    private static int APP_WIDTH = 600;
    private static int APP_HEIGHT = 400;

    private static final long serialVersionUID = -8019663913250286271L;

    public ViewPanel() {
        setBackground(Color.GRAY);
    }

    public void init() {
    }

    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent( g );

        render( g );
    }

    // Where I do the drawing. It's called from the rendering loop in the JFrame
    public void render( Graphics g ) {

        // refresh the background since we're not relying on paintComponent all the time
        Color bgc = getBackground();
        g.setColor( bgc );
        g.fillRect( 0, 0, APP_WIDTH, APP_HEIGHT );

        // just paint a moving box
        drawBox( g );

        // draw a line to prove correctness. In the loop, you can see part of this line is hidden
        // underneath the title bar
        g.setColor( Color.red );
        g.drawLine(0, 0, APP_WIDTH, APP_HEIGHT);
    }

    protected void drawBox( Graphics g ) {

        // get a random color
        Random ran = new Random();
        int red = ran.nextInt( 255 );
        int grn = ran.nextInt( 255 );
        int ble = ran.nextInt( 255 );
        Color colour = new Color( red, grn, ble );
        g.setColor( colour );

        // get a random position        
        int x = ran.nextInt( APP_WIDTH - 50);
        int y = ran.nextInt( APP_HEIGHT - 50);

        // draw it
        g.fillRect( x, y, 50, 50 );
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Frecklefoot
  • 1,660
  • 2
  • 21
  • 52
  • 2
    No, no, no a thousand times no. Swing uses a passive rendering engine, you can not replace this with our implementation. When a component is painted, it's origin point is translated so that it's `0x0` position is the top left position. In your example, the `BufferStrategy` belongs to the `JFrame`, so the `Graphics` is related to the context of the `JFrame` - so basically, it's just a massive mess – MadProgrammer Oct 31 '18 at 21:39
  • 1
    What MadProgrammer said. If you obtain a BufferStrategy from a Frame, you must use it to paint that Frame; if you obtain a BufferStrategy from a Canvas, you must use it to paint that Canvas. A BufferStrategy and its Graphics cannot be transferred to other components. – VGR Oct 31 '18 at 21:45
  • @vgr You can pass the `Graphic`s context from the `BufferStrategy` to where ever you want, the source of the strategy will simply be used to render the result. In most cases, this means you should avoid any of the implemented "painting" routes of the components and paint onto the `Graphics` context in a independent manner - this might be what you were saying and I might have just read it wrong - but your basic gist is correct ;) – MadProgrammer Oct 31 '18 at 21:48
  • That clears up everything! Thanks! – Frecklefoot Oct 31 '18 at 22:02
  • @MadProgrammer To be fair: Active rendering **is** possible (when implemented properly). Although in ~20 years of Swing Programming, I never really used it. Some games might have a justification for using it, but when the question is: "Is it necessary (or even just appropriate) for the task in this question?", then the answer is certainly "No, no, no..." – Marco13 Oct 31 '18 at 22:17
  • @Marco13 Active rendering is very possible - just not through Swing, which has it's own painting mechanism. This is what's `BufferStrategy` is for. It gives YOU control over the painting mechanism. I've read many blogs trying to explain how to get Swing and `BufferStrategy` and I'm yet to see a solution which "just works" and "works well" – MadProgrammer Oct 31 '18 at 22:41

1 Answers1

1

Swing uses it's own rendering engine, which is a passive implementation. You're attempting to circumvent this with your own, active, rendering engine, the two are going to butt heads.

Because the BufferStrategy belongs to the JFrame, it's created within the confines of it, so 0x0 will be the top left position of the JFrame, not the JPanel.

Swing's rendering engine will do this translation automatically for you.

You have two basic choices.

  1. Don't base the rendering off a JPanel and simply have a "render" class which does this independently (and use a Canvas instead of the JFrame as the bases for the BufferStrategy)
  2. Use a Swing Timer as the primary rendering engine

Swing Timer based example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new ViewPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class ViewPanel extends JPanel {

        private static int APP_WIDTH = 600;
        private static int APP_HEIGHT = 400;

        private static final long serialVersionUID = -8019663913250286271L;

        public ViewPanel() {
            setBackground(Color.GRAY);
            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    repaint();
                }
            });
            timer.start();
        }

        public void init() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(APP_HEIGHT, APP_HEIGHT);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            render(g);
        }

        // Where I do the drawing. It's called from the rendering loop in the JFrame
        public void render(Graphics g) {

            // refresh the background since we're not relying on paintComponent all the time
            Color bgc = getBackground();
            g.setColor(bgc);
            g.fillRect(0, 0, APP_WIDTH, APP_HEIGHT);

            // just paint a moving box
            drawBox(g);

            // draw a line to prove correctness. In the loop, you can see part of this line is hidden
            // underneath the title bar
            g.setColor(Color.red);
            g.drawLine(0, 0, APP_WIDTH, APP_HEIGHT);
        }

        protected void drawBox(Graphics g) {

            // get a random color
            Random ran = new Random();
            int red = ran.nextInt(255);
            int grn = ran.nextInt(255);
            int ble = ran.nextInt(255);
            Color colour = new Color(red, grn, ble);
            g.setColor(colour);

            // get a random position        
            int x = ran.nextInt(APP_WIDTH - 50);
            int y = ran.nextInt(APP_HEIGHT - 50);

            // draw it
            g.fillRect(x, y, 50, 50);
        }
    }
}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366