Не удается скрыть SystemTray JPopupMenu, когда он теряет фокус


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

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, а не от обычной кнопки, я не могу использовать решение, упомянутое здесь , чтобы обойти FocusListener, не вызывая focusLost.

В конечном счете, то, что мне интересно, это либо :
1) Есть есть способ получить внешний вид системы для нормального AWT PopupMenu?, или
2) есть ли способ заставить JPopupMenu исчезнуть, когда он теряет фокус?


EDIT: по запросу, вот мой 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 2

3 ответа:

Я нашел хак, который, как я чувствую, будет работать просто отлично. Я еще не тестировал его в Windows XP, Но он работает в Windows 7. Это включает в себя добавление "скрытого диалога", который отображает позади всплывающего меню, как если бы всплывающее меню возникло из скрытого диалога в первую очередь. Единственный реальный трюк-это заставить скрытый диалог оставаться за всплывающим меню. По крайней мере, в Windows 7 он отображается за системным Треем, поэтому вы никогда не увидите его в первую очередь. A 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 для работы в системе лоток в CentOS / RedHat Linux. Для этого мне придется просто использовать обычный AWT PopupMenu.

JPopupMenu не может быть отображен сам по себе. То есть его нужно добавить в окно. Попробуйте использовать WindowListener, а затем скрыть всплывающее окно в событии windowDeactivated (). После того, как всплывающее окно будет видно, вы должны быть в состоянии получить окно с помощью:

Window window = SwingUtilities.windowForComonent(systemTrayPopupMenu);

Я только что использовал MouseListener в меню JPopup, который вызывает поток таймера при выходе мыши; если мышь снова входит, Я сбрасываю флаг "mouseStillOnMenu". Установите "нить.sleep() значение, равное тому, как долго вы хотите, чтобы пользователь мог покинуть меню - если вы обычно нажимаете на пункт меню a, вызывается поведение закрытия меню по умолчанию и закрывает меню.

@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();

}