0

I've been struggling to get this to work. The only solution (but terrible solution) I found was re creating a new window every frame which is horrible for performance and other obvious reasons. Here is what I have but I'm unsure about how I can go about redrawing every frame. Or if there's another approach I can take to achieve the same functionality, how can I go about doing it?

public static Window w = null;

@SuppressWarnings("serial")
    private static void initializeWindow(final Dimension d) {
        int windowHeight = d.height - 1; 
        
        w = new Window(null) {
            
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D g2d = (Graphics2D) g;
                        //painting goes here (but it only seems to draw for one frame)
                        //for some repainting doesn't seem to work
            }
            
            @Override
            public void update(Graphics g) {
                paint(g);
                System.out.println(true);
            }
            
        };
        
        w.setAlwaysOnTop(true);
        w.setBounds(0, 0, d.width, windowHeight);
        w.setBackground(new Color(0, true));
        w.setVisible(true);
    }

I tried the code block attached and I expected to be able to update the window every frame with newly painted stuff.

Novality
  • 3
  • 1
  • 3
  • I don't see anything in your code that would cause the Window to repaint itself. Not really sure what you are trying to do. See: https://stackoverflow.com/a/54028681/131872 for an example uses Swing and a Timer to do aniimation. – camickr Jul 10 '23 at 21:20
  • Don't override `paint` of top level containers - they aren't double buffered – MadProgrammer Jul 10 '23 at 22:30
  • @camickr i didn't include my entire program but I was calling w.update(); when appropriate – Novality Jul 10 '23 at 22:53
  • @Novality Since `update` requires a `Graphics` context to passed to it - where are you getting the `Graphics` context from? If you're using `getGraphics`, then this is not the right approach, as `getGraphics` returns a snapshot of the context previously used to paint the component, which will be painted over on the next refresh of the window. I suggest looking at [Painting in AWT and Swing](https://www.oracle.com/java/technologies/painting.html) to get a better understand of how the paint process actually works – MadProgrammer Jul 11 '23 at 00:03
  • update method should be w.repaint() not paint(g) , you could also use w.createBufferStrategy(2) or such. – Samuel Marchant Jul 11 '23 at 00:49

2 Answers2

0

You problem is pretty basic - you need some way to update the UI on a regular bases.

Your problem is further complicated by the fact that Swing is single thread and NOT thread safe. Lucky for you, the Swing team fore saw this issue and provided the Swing Timer class.

Start by seeing Concurrency in Swing and How to Use Swing Timers for more details.

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
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 window = new JFrame();
                window.setUndecorated(true);
                window.setBackground(new Color(0, true));
                window.add(new TestPane());
                window.pack();
                window.setLocationRelativeTo(null);
                window.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        enum Direction {
            UP, DOWN;
        }

        private Timer timer;

        private Direction direction = Direction.UP;
        private double yDelta = 2;
        private double yPos = 380;

        private Shape ball = new Ellipse2D.Double(0, 0, 20, 20);

        public TestPane() {
            setOpaque(false);

            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (direction == Direction.UP) {
                        yPos -= yDelta;
                        yDelta -= 0.006;
                        if (yDelta <= 0.0) {
                            yDelta = 0;
                            direction = Direction.DOWN;
                        }
                        if (yPos < 0) {
                            yPos = 0;
                            direction = Direction.DOWN;
                        }
                    } else if (direction == Direction.DOWN) {
                        yPos += yDelta;
                        yDelta += 0.01;
                        if (yDelta > 2.0) {
                            yDelta = 2.0;
                        }
                        if (yPos + ball.getBounds().height > getHeight()) {
                            yPos = getHeight() - ball.getBounds().height;
                            direction = Direction.UP;
                            yDelta = 2;
                        }
                    }
                    repaint();
                }
            });
        }

        @Override
        public void addNotify() {
            super.addNotify();
            timer.start();
        }

        @Override
        public void removeNotify() {
            timer.stop();
            super.removeNotify();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            g2d.setColor(Color.RED);
            g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
            g2d.translate(190, yPos);
            g2d.fill(ball);
            g2d.dispose();
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

MadProgrammer's solution was mostly correct. But I went a step further to address a few things.

I changed the JFrame to a JWindow and removed the call to setUndecorated. This kept functionality and removed the window from the taskbar.

Next and optionally, I used JNA to allow all inputs to pass through the window's components. My final solution is as follows, thanks for all of the help:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.Timer;
import com.sun.jna.*;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinUser;

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JWindow window = new JWindow();
                window.setBackground(new Color(0, true));
                window.add(new TestPane());
                window.pack();
                window.setLocationRelativeTo(null);
                window.setVisible(true);
                window.setAlwaysOnTop(true);
                setTransparent(window);
            }
        });
    }

    public static class TestPane extends JPanel {
        enum Direction {
            UP, DOWN;
        }

        private Timer timer;

        private Direction direction = Direction.UP;
        private double yDelta = 2;
        private double yPos = 380;

        private Shape ball = new Ellipse2D.Double(0, 0, 20, 20);

        public TestPane() {
            setOpaque(false);

            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (direction == Direction.UP) {
                        yPos -= yDelta;
                        yDelta -= 0.006;
                        if (yDelta <= 0.0) {
                            yDelta = 0;
                            direction = Direction.DOWN;
                        }
                        if (yPos < 0) {
                            yPos = 0;
                            direction = Direction.DOWN;
                        }
                    } else if (direction == Direction.DOWN) {
                        yPos += yDelta;
                        yDelta += 0.01;
                        if (yDelta > 2.0) {
                            yDelta = 2.0;
                        }
                        if (yPos + ball.getBounds().height > getHeight()) {
                            yPos = getHeight() - ball.getBounds().height;
                            direction = Direction.UP;
                            yDelta = 2;
                        }
                    }
                    repaint();
                }
            });
        }

        @Override
        public void addNotify() {
            super.addNotify();
            timer.start();
        }

        @Override
        public void removeNotify() {
            timer.stop();
            super.removeNotify();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            g2d.setColor(Color.RED);
            g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
            g2d.translate(190, yPos);
            g2d.fill(ball);
            g2d.dispose();
        }
    }
    
    private static void setTransparent(Component w) {
        WinDef.HWND hwnd = getHWnd(w);
        int wl = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE);
        wl = wl | WinUser.WS_EX_LAYERED | WinUser.WS_EX_TRANSPARENT;
        User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, wl);
    }

    /**
     * Get the window handle from the OS
     */
    private static HWND getHWnd(Component w) {
        HWND hwnd = new HWND();
        hwnd.setPointer(Native.getComponentPointer(w));
        return hwnd;
    }
}
Novality
  • 3
  • 1
  • 3