Java Swing: постоянная подсказка всплывающей подсказки, которая следует за курсором мыши в JFrame приложения

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

GlobalToolTip panelHint = new GlobalToolTip("Global hint message.");
panelHint.show(); //Will remain visible and follow mouse
... //Waiting for user to perform a specific action or cancel
panelHint.hide(); //Hidden/destroyed here

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

Кажется, что это должно быть довольно легко решить проблему, но я не смог. Любая помощь будет оценена по достоинству.


person MasterHD    schedule 15.01.2021    source источник
comment
Покажите нам какой-нибудь код для решения GlassPane, которое у вас было, так как это было бы моим предложением. В противном случае мы все будем тратить свое время на поиск решения только для того, чтобы вы сказали нам, что оно не работает при использовании компонента X. Где, как если бы вы показали какой-то код, который мы можем использовать для воспроизведения проблемы, возможно, мы сможем найти решение   -  person David Kroukamp    schedule 15.01.2021
comment
@DavidKroukamp Я был готов опубликовать некоторый код (очищенный, чтобы скрыть внешние эффекты проекта), но элегантное решение VGR делает именно то, что я хотел, без необходимости использования всплывающих подсказок или стеклянных панелей, поэтому я решил пойти по этому пути.   -  person MasterHD    schedule 18.01.2021


Ответы (1)


Лично я не стал бы возиться со GlassPane и не стал бы пытаться заставить JToolTip или ToolTipManager делать то, для чего они не предназначены. Я бы просто сделал JLabel, который имитирует JToolTip:

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Window;

import java.awt.event.MouseEvent;
import java.awt.event.AWTEventListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class FollowTip {
    private JWindow tip;

    private final AWTEventListener mouseHandler = e -> {
        Window window = tip.getOwner();
        MouseEvent event = null;

        switch (e.getID()) {
            case MouseEvent.MOUSE_ENTERED:
            case MouseEvent.MOUSE_MOVED:
            case MouseEvent.MOUSE_DRAGGED:
                event = (MouseEvent) e;
                if (window.isAncestorOf(event.getComponent())) {
                    Point loc = event.getLocationOnScreen();
                    tip.setLocation(loc.x + 10, loc.y + 10);
                    tip.setVisible(true);
                }
                break;
            case MouseEvent.MOUSE_EXITED:
                event = (MouseEvent) e;
                Point p = SwingUtilities.convertPoint(
                    event.getComponent(), event.getPoint(), window);
                if (!window.contains(p)) {
                    tip.setVisible(false);
                }
                break;
            default:
                break;
        }
    };

    public FollowTip(String text,
                     Window window) {

        JLabel tipLabel = new JLabel(text);

        tipLabel.setForeground(UIManager.getColor("ToolTip.foreground"));
        tipLabel.setBackground(UIManager.getColor("ToolTip.background"));
        tipLabel.setFont(UIManager.getFont("ToolTip.font"));
        tipLabel.setBorder(UIManager.getBorder("ToolTip.border"));

        tip = new JWindow(window);
        tip.setType(Window.Type.POPUP);
        tip.setFocusableWindowState(false);
        tip.getContentPane().add(tipLabel);
        tip.pack();
    }

    public void activate() {
        Window window = tip.getOwner();
        window.getToolkit().addAWTEventListener(mouseHandler,
            AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);

        Point p = window.getMousePosition();
        if (p != null) {
            SwingUtilities.convertPointToScreen(p, window);
            tip.setLocation(p.x + 10, p.y + 10);
            tip.setVisible(true);
        }
    }

    public void deactivate() {
        Window window = tip.getOwner();
        window.getToolkit().removeAWTEventListener(mouseHandler);

        tip.setVisible(false);
    }

    static void showWindow() {
        Object[][] data = new Object[12][];
        Object[] headings = new Object[data.length];
        for (int i = 0; i < data.length; i++) {
            data[i] = new Object[data.length];
            for (int j = 0; j < data[i].length; j++) {
                data[i][j] = (i + 1) * (j + 1);
            }
            headings[i] = i;
        }

        JTable table = new JTable(data, headings);

        JToggleButton button = new JToggleButton("Active");
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(button);

        JFrame frame = new JFrame("FollowTip");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new JScrollPane(table));
        frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        FollowTip tip = new FollowTip("This is a tip", frame);

        button.addActionListener(e -> {
            if (button.isSelected()) {
                tip.activate();
            } else {
                tip.deactivate();
            }
        });
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> showWindow());
    }
}
person VGR    schedule 15.01.2021
comment
Спасибо за элегантное решение, в основном оно работает! В основном фрейме есть определенные области, в которых движение мыши не обновляется, в частности, пустые области панели инструментов и панели меню. Вы знаете, почему это может быть? - person MasterHD; 18.01.2021
comment
Кроме того, метка не очень хорошо имитирует внешний вид всплывающей подсказки, но вместо JLabel tipLabel на самом деле можно использовать напрямую JToolTip и скрыть фон всплывающего окна tip.setBackground(new Color(1.f,1.f,1.f,0.f)); - person MasterHD; 18.01.2021
comment
Если «пустые области панели инструментов и панели меню» относятся к предоставленному системой оформлению окна, это не находится под контролем программы, и я сомневаюсь, что вы можете что-то с этим поделать. - person VGR; 18.01.2021