java - 失去焦点时无法隐藏 SystemTray JPopupMenu

标签 java swing system-tray jpopupmenu focuslistener

这个问题类似于this one .我拥有的是一个从系统托盘上的图标弹出的 JPopupMenu。此时,系统托盘是程序的唯一体现。也就是说,没有其他窗口打开,系统托盘中的图标是我访问该程序的唯一途径。我在 AWT PopupMenu 上使用了 JPopupMenu,因为我想将系统外观应用于弹出菜单 - 当我只使用普通的 PopupMenu, 获取不到系统的Look and Feel,一直获取Swing的Metal Look and Feel。我使用此解决方法来获得此行为(描述 here ):

systemTrayPopupMenu = buildSystemTrayJPopupMenu();
trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */);
trayIcon.addMouseListener (new MouseAdapter () {
    @Override
    public void mouseReleased (MouseEvent me) {
        if (me.isPopupTrigger()) {
            systemTrayPopupMenu.setLocation(me.getX(), me.getY());
            systemTrayPopupMenu.setInvoker(systemTrayPopupMenu);
            systemTrayPopupMenu.setVisible(true);
        }
    }
};

当我右键单击托盘图标时,它会显示菜单,自然地,当我进行选择时,菜单会消失。但是,当我调出菜单,然后单击它时,它并没有消失。要使其当前消失,我必须进行选择,或者选择禁用的菜单项之一。

我尝试向它添加一个 FocusListener,但是,没有迹象表明 focusLostfocusGained 方法曾经被调用过。此外,当另一个 Window 获得焦点时,我不能让它消失,因为没有其他窗口存在。由于此弹出菜单来自 TrayIcon 而不是典型的按钮,因此我无法使用提到的解决方案 here绕过 FocusListener 而不是调用 focusLost

最终,我想知道的是要么:
1) 有没有办法让系统的外观和感觉适合普通的 AWT PopupMenu?或者
2) 有没有办法让 JPopupMenu 在失去焦点时消失?


编辑:根据要求,这是我的SSCCE:

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

public class SwingSystemTray {

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run () {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                new SwingSystemTray ();
            } catch (Exception e) {
                System.out.println("Not using the System UI defeats the purpose...");
                e.printStackTrace();
            }
        }
    });
}

protected SystemTray systemTray;
protected TrayIcon trayIcon;
protected JPopupMenu systemTrayPopupMenu;
protected Image iconImage;

public SwingSystemTray () throws IOException {
    iconImage = getIcon ();
    if (SystemTray.isSupported()) {
        systemTray = SystemTray.getSystemTray();
        systemTrayPopupMenu = buildSystemTrayJPopupMenu();
        trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */);
        trayIcon.addMouseListener (new MouseAdapter () {
            @Override
            public void mouseReleased (MouseEvent me) {
                if (me.isPopupTrigger()) {
                    systemTrayPopupMenu.setLocation(me.getX(), me.getY());
                    systemTrayPopupMenu.setInvoker(systemTrayPopupMenu);
                    systemTrayPopupMenu.setVisible(true);
                }
            }
        });
        try {
            systemTray.add(trayIcon);
        } catch (AWTException e) {
            System.out.println("Could not place item at tray.  Exiting.");
        }
    }
}

protected JPopupMenu buildSystemTrayJPopupMenu () {
    final JPopupMenu menu = new JPopupMenu ();
    final JMenuItem showMenuItem = new JMenuItem("Show");
    final JMenuItem hideMenuItem = new JMenuItem("Hide");
    final JMenuItem exitMenuItem = new JMenuItem("Exit");
    hideMenuItem.setEnabled(false);
    ActionListener listener = new ActionListener () {
        @Override
        public void actionPerformed (ActionEvent ae) {
            Object source = ae.getSource();
            if (source == showMenuItem) {
                System.out.println("Shown");
                showMenuItem.setEnabled(false);
                hideMenuItem.setEnabled(true);
           }
           else if (source == hideMenuItem) {
                System.out.println("Hidden");
                hideMenuItem.setEnabled(false);
                showMenuItem.setEnabled(true);
            }
            else if (source == exitMenuItem) {
                System.exit(0);
            }
        }
    };
    for (JMenuItem item : new JMenuItem [] {showMenuItem, hideMenuItem, exitMenuItem}) {
        if (item == exitMenuItem) menu.addSeparator();
        menu.add(item);
        item.addActionListener(listener);
    }
    return menu;
}

protected Image getIcon () throws IOException {
    // Build the 16x16 image programmatically, start with BMP Header
    byte [] iconData = new byte[822];
    System.arraycopy(new byte [] {0x42,0x4d,0x36,0x03, 0,0,0,0, 0,0,0x36,0, 
            0,0,0x28,0, 0,0,16,0, 0,0,16,0, 0,0,16,0, 24,0,0,0, 0,0,0,3},
            0, iconData, 0, 36);
    for (int i = 36; i < 822; iconData[i++] = 0);
    for (int i = 56; i < 822; i += 3) iconData[i] = -1;     
    return ImageIO.read(new java.io.ByteArrayInputStream(iconData));
}
}

最佳答案

我发现了一个我觉得会很好用的 hack。我还没有在 Windows XP 中测试它,但它在 Windows 7 中工作。这涉及添加一个“隐藏对话框”,它显示在弹出菜单的后面,就好像弹出菜单源自隐藏对话框第一名。唯一真正的技巧是让隐藏的对话框留在弹出菜单后面。至少在 Windows 7 中,它显示在系统托盘的后面,所以您从一开始就看不到它。可以将 WindowFocusListener 添加到此隐藏对话框中,因此当您单击弹出菜单外时,您也单击了隐藏对话框外。我已将此功能添加到我之前发布的 SSCCE 中,以说明添加此功能的工作原理:

package org.test;

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

public class SwingSystemTray {

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run () {
            try {
                /* We are going for the Windows Look and Feel here */
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                new SwingSystemTray ();
            } catch (Exception e) {
                System.out.println("Not using the System UI defeats the purpose...");
                e.printStackTrace();
            }
        }
    });
}

protected SystemTray systemTray;
protected TrayIcon trayIcon;
protected JPopupMenu systemTrayPopupMenu;
protected Image iconImage;
/* Added a "hidden dialog" */
protected JDialog hiddenDialog;

public SwingSystemTray () throws IOException {
    iconImage = getIcon ();
    if (SystemTray.isSupported()) {
        systemTray = SystemTray.getSystemTray();
        systemTrayPopupMenu = buildSystemTrayJPopupMenu();
        trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */);
        trayIcon.addMouseListener (new MouseAdapter () {
            @Override
            public void mouseReleased (MouseEvent me) {
                if (me.isPopupTrigger()) {
                    systemTrayPopupMenu.setLocation(me.getX(), me.getY());
                    /* Place the hidden dialog at the same location */
                    hiddenDialog.setLocation(me.getX(), me.getY());
                    /* Now the popup menu's invoker is the hidden dialog */
                    systemTrayPopupMenu.setInvoker(hiddenDialog);
                    hiddenDialog.setVisible(true);
                    systemTrayPopupMenu.setVisible(true);
                }
            }
        });
        trayIcon.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed (ActionEvent ae) {
                System.out.println("actionPerformed");
            }
        });
        try {
            systemTray.add(trayIcon);
        } catch (AWTException e) {
            System.out.println("Could not place item at tray.  Exiting.");
        }
    }
    /* Initialize the hidden dialog as a headless, titleless dialog window */
    hiddenDialog = new JDialog ();
    hiddenDialog.setSize(10, 10);
    /* Add the window focus listener to the hidden dialog */
    hiddenDialog.addWindowFocusListener(new WindowFocusListener () {
        @Override
        public void windowLostFocus (WindowEvent we ) {
            hiddenDialog.setVisible(false);
        }
        @Override
        public void windowGainedFocus (WindowEvent we) {}
    });
}

protected JPopupMenu buildSystemTrayJPopupMenu () {
    final JPopupMenu menu = new JPopupMenu ();
    final JMenuItem showMenuItem = new JMenuItem("Show");
    final JMenuItem hideMenuItem = new JMenuItem("Hide");
    final JMenuItem exitMenuItem = new JMenuItem("Exit");
    hideMenuItem.setEnabled(false);
    ActionListener listener = new ActionListener () {
        @Override
        public void actionPerformed (ActionEvent ae) {
            /* We want to make sure the hidden dialog goes away after selection */
            hiddenDialog.setVisible(false);
            Object source = ae.getSource();
            if (source == showMenuItem) {
                System.out.println("Shown");
                showMenuItem.setEnabled(false);
                hideMenuItem.setEnabled(true);
            }
            else if (source == hideMenuItem) {
                System.out.println("Hidden");
                hideMenuItem.setEnabled(false);
                showMenuItem.setEnabled(true);
            }
            else if (source == exitMenuItem) {
                System.exit(0);
            }
        }
    };
    for (JMenuItem item : new JMenuItem [] {showMenuItem, hideMenuItem, exitMenuItem}) {
        if (item == exitMenuItem) menu.addSeparator();
        menu.add(item);
        item.addActionListener(listener);
    }
    return menu;
}

protected Image getIcon () throws IOException {
    // Build the 16x16 image programmatically, start with BMP Header
    byte [] iconData = new byte[822];
    System.arraycopy(new byte [] {0x42,0x4d,0x36,0x03, 0,0,0,0, 0,0,0x36,0, 
            0,0,0x28,0, 0,0,16,0, 0,0,16,0, 0,0,16,0, 24,0,0,0, 0,0,0,3},
            0, iconData, 0, 36);
    for (int i = 36; i < 822; iconData[i++] = 0);
    for (int i = 56; i < 822; i += 3) iconData[i] = -1;        
    return ImageIO.read(new java.io.ByteArrayInputStream(iconData));
}
}

此解决方案为我提供了我一直在寻找的要求 #2,即当 JPopupMenu 使用 Windows 系统外观和感觉在系统托盘上失去焦点时,它会消失。

注意:我还没有在 CentOS/RedHat Linux 的系统托盘上使用 JPopupMenu 功能。对于那些,我将不得不使用普通的 AWT PopupMenu

关于java - 失去焦点时无法隐藏 SystemTray JPopupMenu,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19868209/

相关文章:

java - Maven2 绑定(bind)到自定义阶段

java - 我可以为对象的每个实例创建一个循环吗?

java - 引用选项卡 Pane 中的按钮

Java - 将鼠标事件发送到Applet

winapi - 获得托盘区域时钟视觉主题的最佳方式是什么?

java - Windows 10 通知中的空白图标

java - AppCompatActivity 的接口(interface)

java - Eclipse Tomcat 创建 3 个重复的 JNDI 连接池

java - Swing GTK L&F 标题栏上的额外按钮

c# - 最大化系统托盘中的应用程序?