当它失去焦点时,无法隐藏SystemTray JPopupMenu

时间:2013-11-08 20:45:27

标签: java swing system-tray jpopupmenu focuslistener

此问题与this one类似。我所拥有的是从系统托盘上的图标弹出的JPopupMenu。此时,系统托盘是程序的唯一表现形式。也就是说,没有其他窗口打开,系统托盘中的图标是我可以访问该程序的唯一方法。我在AWT JPopupMenu上使用PopupMenu,因为我想将系统外观应用到弹出菜单中 - 当我只使用普通PopupMenu时,我无法获得系统的外观和感觉,我只是不断获得Swing的金属外观和感觉。我使用这种方法来解决这种问题(描述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));
}
}

3 个答案:

答案 0 :(得分:3)

我发现了一个我认为可以很好地工作的黑客。我还没有在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系统外观失去对系统托盘的关注时,JPopupMenu会消失。

注意:我没有使用PopupMenu功能在CentOS / RedHat Linux中的系统托盘上工作。对于那些人,我将只需使用普通的AWT {{1}}。

答案 1 :(得分:0)

JPopupMenu无法单独显示。那就是需要将它添加到窗口中。尝试使用WindowListener,然后在windowDeactivated()事件中隐藏弹出窗口。弹出窗口可见后,您应该可以使用以下命令获取窗口:

Window window = SwingUtilities.windowForComonent(systemTrayPopupMenu);

答案 2 :(得分:-1)

我刚刚在JPopup菜单上使用了MouseListener,它在鼠标退出时调用了一个计时器Thread;如果鼠标重新进入,我重置&#34; mouseStillOnMenu&#34;旗。将&#34; Thread.sleep()值设置为您希望用户离开菜单的时间长 - 如果您正常单击菜单项,则会调用默认菜单关闭行为并关闭菜单。

@Override
public void mouseEntered(MouseEvent arg0) {
    mouseStillOnMenu = true;

}

@Override
public void mouseExited(MouseEvent arg0) {
    mouseStillOnMenu = false;

    new Thread(new Runnable() {

        @Override
        public void run() {

            try {
                Thread.sleep(1000);  //waits one second before checking if mouse is still on the menu
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (!isMouseStillOnMenu()) {
                jpopup.setVisible(false);
            }

        }

    }).start();

}