【问题标题】:Cannot hide SystemTray JPopupMenu when it loses focus失去焦点时无法隐藏 SystemTray JPopupMenu
【发布时间】:2013-11-08 20:45:27
【问题描述】:

这个问题类似于this one。我所拥有的是从系统托盘上的图标弹出的JPopupMenu。此时,系统托盘是程序的唯一体现。也就是说,没有其他窗口打开,系统托盘中的图标是我访问该程序的唯一途径。我在 AWT PopupMenu 上使用了 JPopupMenu,因为我想将系统外观应用到弹出菜单 - 当我只使用普通的 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,但是没有迹象表明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));
}
}

【问题讨论】:

  • 这实际上是许多托盘图标的问题......因为他们没有父焦点的概念来知道焦点何时实际改变。我见过的最好的解决方案是简单地在菜单末尾放置一个“关闭”选项并让用户关闭它;)。我没试过,但我也想过给转义键添加一个键绑定......
  • 发布您的SSCCE 来证明问题。

标签: java swing system-tray jpopupmenu focuslistener


【解决方案1】:

我发现了一个我觉得会很好用的 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。

【讨论】:

  • 谢谢你 - 非常有用。不幸的是,它并不能帮助我解决 OSX 上的问题,但这是一个很好的尝试! :)
【解决方案2】:

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

Window window = SwingUtilities.windowForComonent(systemTrayPopupMenu);

【讨论】:

  • SystemTray 只知道 awt.Popup,这个问题没有直接的答案,看起来就像 OP 有冰球一样
【解决方案3】:

我刚刚在 JPopup 菜单上使用了一个 MouseListener,它在鼠标退出时调用一个计时器线程;如果鼠标重新进入,我会重置“mouseStillOnMenu”标志。将“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();

}

【讨论】:

    猜你喜欢
    • 2012-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-07
    • 2012-01-11
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    相关资源
    最近更新 更多