Question

This question is similar to this one. What I have is a JPopupMenu that pops up from an icon on the system tray. At this point, the system tray is the only manifestation of the program. That is, there are no other windows open, the icon in the system tray is the only way I can access the program. I used a JPopupMenu over the AWT PopupMenu because I wanted to get the system Look and Feel applied to the popup menu - when I used just a plain PopupMenu, I could not get the system's Look and Feel, I just kept getting Swing's Metal Look and Feel. I used this work-around to get this behavior (described 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);
        }
    }
};

When I right click on the tray icon, it shows the menu, and naturally, when I make a selection, the menu disappears. However, when I bring up the menu, then click out of it, it does not disappear. To make it disappear currently, I have to either make a selection, or select one of the menu items that are disabled.

I tried adding a FocusListener to it, however, there is no indication that the focusLost or focusGained methods ever get called. Additionally, I cannot make it disappear when another Window gains focus because there are no other windows present. Since this pop-up menu comes from a TrayIcon and not a typical button, I cannot use the solution mentioned here to get around the FocusListener not calling focusLost.

Ultimately, what I am wondering is either:
1) Is there a way to get the system's look and feel for a normal AWT PopupMenu?, or
2) Is there a way to make the JPopupMenu disappear when it loses focus?


EDIT: Per request, here is my 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));
}
}
Was it helpful?

Solution

I found a hack that I feel will work just nicely. I have yet to test it in Windows XP, but it works in Windows 7. This involves adding a "hidden dialog" that displays behind the popup menu, as if the popup menu originated from the hidden dialog in the first place. The only real trick is getting the hidden dialog to stay behind the popup menu. At least in Windows 7, it displays behind the system tray, so you never really see it in the first place. A WindowFocusListener can be added to this hidden dialog, and so when you click out of the popup menu, you are also clicking out of the hidden dialog. I have added this capability to the SSCCE that I posted previously to illustrate how adding this works:

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));
}
}

This solution gives me requirement #2 that I was looking for, which is to make the JPopupMenu disappear when it loses focus on a system tray using the Windows system look and feel.

Note: I have not gotten the JPopupMenu feature to work on the system tray in CentOS/RedHat Linux. For those, I will have to just use a normal AWT PopupMenu.

OTHER TIPS

A JPopupMenu can't be displayed by itself. That is it needs to be added to a window. Try to use a WindowListener and then hide the popup on a windowDeactivated() event. After the popup is visible you should be able to get the window by using:

Window window = SwingUtilities.windowForComonent(systemTrayPopupMenu);

I just used a MouseListener on the JPopup menu which invokes a timer Thread upon mouse exit; if the mouse re-enters, I reset the "mouseStillOnMenu" flag. Set the "Thread.sleep() value to however long you want the user to be able leave the menu - if you click on a a menu item normally, the default menu close behavior is invoked and closes the menu.

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

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top