Java: Making a window click-through (including text/images)
Asked Answered
L

3

9

I want to create an overlay in Java that is transparent, always on top, and that I can click-through. I've found some similar posts about this issue, but even after following their answers, I'm having one issue.

My problem is making the whole window click-through. I'm not having any problem making it work with a JFrame, but once I add any components to it (JLabel or an ImagePanel), the click-through attribute doesn't carry over to them.

As I want to have a background image for my application this basically makes the code I have useless seeing how the window gets focused whenever I click the area the text/image covers.

Before I show the code I'm using I'd first like to refer to these threads which essentially describes precisely what I want, except in C#.

My goal is to create an overlay with a transparent .png-image and some text on-top that will change on key events. If it uses JFrame or any other library doesn't matter. I only need it compatible with Windows.

I'd also like to mention that I've got some experience with Java, but am a novice in using JFrame.

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;

import com.sun.jna.platform.WindowUtils;


public class Overlay {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Overlay Window");
        frame.setUndecorated(true);
        frame.setAlwaysOnTop(true);
        frame.getRootPane().putClientProperty("apple.awt.draggableWindowBackground", false);
        frame.setLocation(400, 400);
        frame.getContentPane().setLayout(new java.awt.BorderLayout());

        JLabel textLabel = new JLabel("I'm a label in the window", SwingConstants.CENTER);
        frame.getContentPane().add(textLabel, BorderLayout.CENTER); 
        frame.pack();

        System.setProperty("sun.java2d.noddraw", "true");
        WindowUtils.setWindowTransparent(frame, true);
        WindowUtils.setWindowAlpha(frame, 1.0f);

        //Using AWTUtilities gives the same result as WindowUtils
        //AWTUtilities.setWindowOpaque(frame, false);
        //AWTUtilities.setWindowOpacity(frame, 1.0f);

        frame.setVisible(true);
    }
}

Note that the problem is not about the window being focused (though that is a result of the issue), but about the JLabel and ImagePanel not being click-through.

Lacewing answered 27/6, 2012 at 0:7 Comment(2)
You probably need to set the WS_EX_TRANSPARENT flag on the JFrame's window to ensure that mouse events are passed through that window. Here is an example: codeproject.com/Articles/12877/Transparent-Click-Through-FormsCzech
Do you want to simply have all clicks go to a single component in the JFrame, or have them pass through to whatever might be beneath the JFrame (including the desktop)?Czech
L
4

The problem with having a window be "click through" is that it's handled on a system level, outside the scope of the standard APIs. This means that any code written to make a window "click through" will be system dependant. That being said, the process for accomplishing this on windows is rather straight forward.

On Windows 2000 and later, by setting the flags WS_EX_LAYERED and WS_EX_TRANSPARENT on a window, the window will then be click through. Example code uses JNA to accomplish this:

public static void main(String[] args) {
    Window w = new Window(null);

    w.add(new JComponent() {
        /**
         * This will draw a black cross on screen.
         */
        protected void paintComponent(Graphics g) {
            g.setColor(Color.BLACK);
            g.fillRect(0, getHeight() / 2 - 10, getWidth(), 20);
            g.fillRect(getWidth() / 2 - 10, 0, 20, getHeight());
        }

        public Dimension getPreferredSize() {
            return new Dimension(100, 100);
        }
    });
    w.pack();
    w.setLocationRelativeTo(null);
    w.setVisible(true);
    w.setAlwaysOnTop(true);
    /**
     * This sets the background of the window to be transparent.
     */
    AWTUtilities.setWindowOpaque(w, false);
    setTransparent(w);
}

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;
}
Lozano answered 27/2, 2015 at 18:57 Comment(0)
C
1

I tried to make fully "event-transparent" (click-through as you call it) window, but there seems to be some native restrictions on that trick.

Check this window example:

public static void main ( String[] args )
{
    Window w = new Window ( null );

    w.add ( new JComponent ()
    {
        protected void paintComponent ( Graphics g )
        {
            g.setColor ( Color.BLACK );
            g.fillRect ( 0, getHeight () / 2 - 10, getWidth (), 20 );
            g.fillRect ( getWidth () / 2 - 10, 0, 20, getHeight () );
        }

        public Dimension getPreferredSize ()
        {
            return new Dimension ( 100, 100 );
        }

        public boolean contains ( int x, int y )
        {
            return false;
        }
    } );

    AWTUtilities.setWindowOpaque ( w, false );
    AWTUtilities.setWindowOpacity ( w, 0.5f );

    w.pack ();
    w.setLocationRelativeTo ( null );
    w.setVisible ( true );
}

Window and component does NOT have any:

  1. Mouse listeners
  2. Mouse motion listeners
  3. Mouse wheel listeners
  4. Key listeners

Also the component should ignore any kind of mouse events EVEN if there are any listeners due to the modified contains method.

As you can see - the area where nothing is painted on the component is event-transparent, but the filled area is not. Unluckly i didn't find any workaround to change that behavior. Seems that some "low-level" java methods are blocking the events.

And this is just a basic JComponent-based example. I don't even say about more complex Swing components like labels, buttons e.t.c. which might have their own event-listeners which could block events.

Cathicathie answered 27/6, 2012 at 14:52 Comment(0)
C
1

Why not just make use of the existing JLayeredPane? This blog post demonstrates putting a wide variety of overlays on a JFrame, including text, images, and dynamically drawn pixels.

Czech answered 1/3, 2015 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.