这个问题类似于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
,但是,没有迹象表明 focusLost
或 focusGained
方法曾经被调用过。此外,当另一个 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/