Java: создание сквозного окна (включая текст/изображения)

Я хочу создать наложение в Java, которое прозрачно, всегда сверху, и что я могу кликать по. Я нашел несколько похожих сообщений об этой проблеме, но даже после их ответов я один вопрос.

Моя проблема заключается в создании всего окна. У меня нет проблем с его работой с JFrame, но как только я добавлю к нему какие-либо компоненты (JLabel или ImagePanel), атрибут click-through не переносится.

Как я хочу иметь фоновое изображение для моего приложения, это в основном делает код, который мне бесполезен, видя, как окно становится сфокусированным всякий раз, когда я нажимаю область, на которую распространяется текст/изображение.

Прежде чем я покажу код, который я использую, я хотел бы сначала обратиться к этим темам который по существу описывает именно то, что я хочу, кроме С#.

Моя цель - создать наложение с прозрачным .png-изображением и некоторым текстом сверху, который изменится на ключевые события. Если он использует JFrame или любую другую библиотеку, это не имеет значения. Мне нужно, чтобы он был совместим с Windows.

Я также хотел бы упомянуть, что у меня есть некоторый опыт работы с Java, но я новичок в использовании 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);
    }
}

Обратите внимание, что проблема не о том, что окно сфокусировано (хотя это результат проблемы), но о JLabel и ImagePanel, не являющихся кликом.

Ответ 1

Проблема с "щелчком" окна заключается в том, что она обрабатывается на системном уровне, за пределами стандартных API. Это означает, что любой код, написанный для создания окна "щелчок", будет зависящим от системы. При этом процесс выполнения этого на окнах довольно прямолинейный.

В Windows 2000 и более поздних версиях, установив флаги WS_EX_LAYERED и WS_EX_TRANSPARENT в окне, окно будет нажато. В примере кода используется JNA, чтобы выполнить следующее:

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

Почему бы просто не использовать существующий JLayeredPane? В этом сообщении в блоге демонстрируется множество разнообразных наложений на JFrame, включая текст, изображения и динамически отрисованные пиксели.

Ответ 3

Я попытался сделать полностью "событие прозрачным" (щелчком по экрану, как вы его называете), но, похоже, некоторые ограничения на этот трюк.

Посмотрите пример этого окна:

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

Окно и компонент НЕ имеют:

  • Мышь-слушатели
  • Слушатели движения мыши
  • Слушатели колесика мыши
  • Ключевые слушатели

Также компонент должен игнорировать любые события мыши EVEN, если есть какие-либо прослушиватели из-за модифицированного метода contains.

Как вы можете видеть - область, где ничего не нарисована на компоненте, прозрачна по событию, но заполненная область - нет. К несчастью, я не нашел обходного пути, чтобы изменить это поведение. Кажется, что некоторые "низкоуровневые" java-методы блокируют события.

И это просто базовый пример на основе JComponent. Я даже не говорю о более сложных компонентах Swing, таких как ярлыки, кнопки e.t.c. которые могут иметь свои собственные события-слушатели, которые могут блокировать события.