Java: Making a window click-through (including tex

2019-01-14 04:37发布

问题:

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.

回答1:

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;
}


回答2:

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.



回答3:

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.