3

So I want to display a JPopupMenu whenever the user clicks the icon in the system tray. However, the task bar could be anywhere on the screen - bottom , top , right, left.

enter image description here

How do I determine where the sys tray is so that I can display the popup?
getX() and getY() can get the coordinates of the click. Can some math be done to display the popup properly ?

A simple explanation and sample code will be appreciated.

Also, if the task bar is hidden, will an exception be generated when I add TrayIcon to the SystemTray?

An SO User
  • 24,612
  • 35
  • 133
  • 221

3 Answers3

9

There's no real way to do this within Swing natively, however, you can derive the possible location with the following...

GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
Rectangle bounds = gd.getDefaultConfiguration().getBounds();
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gd.getDefaultConfiguration());

Rectangle safeBounds = new Rectangle(bounds);
safeBounds.x += insets.left;
safeBounds.y += insets.top;
safeBounds.width -= (insets.left + insets.right);
safeBounds.height -= (insets.top + insets.bottom);

System.out.println("Bounds = " + bounds);
System.out.println("SafeBounds = " + safeBounds);

Area area = new Area(bounds);
area.subtract(new Area(safeBounds));
System.out.println("Area = " + area.getBounds());

Which outputs

Bounds = java.awt.Rectangle[x=0,y=0,width=2560,height=1600]
SafeBounds = java.awt.Rectangle[x=0,y=40,width=2560,height=1560]
Area = java.awt.Rectangle[x=0,y=0,width=2560,height=40]

For my system (note, my task bar is at the top of the screen)

Updated

As demonstrated in my previous answer to your question about tray icons...

public class TestTaskIcon {

  public static void main(String[] args) {

    EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {

        Image img = null;
        try {
          img = ImageIO.read(new File("floppy_disk_red.png"));
        } catch (IOException e) {
          e.printStackTrace();
        }
        TrayIcon ti = new TrayIcon(img, "Tooltip");
        ti.addMouseListener(new MouseAdapter() {
          @Override
          public void mouseClicked(MouseEvent e) {
            Rectangle bounds = getSafeScreenBounds(e.getPoint());
            JPopupMenu popup = new JPopupMenu();
            popup.add(new JLabel("hello"));

            Point point = e.getPoint();

            int x = point.x;
            int y = point.y;
            if (y < bounds.y) {
              y = bounds.y;
            } else if (y > bounds.y + bounds.height) {
              y = bounds.y + bounds.height;
            }
            if (x < bounds.x) {
              x = bounds.x;
            } else if (x > bounds.x + bounds.width) {
              x = bounds.x + bounds.width;
            }

            if (x + popup.getPreferredSize().width > bounds.x + bounds.width) {
              x = (bounds.x + bounds.width) - popup.getPreferredSize().width;
            }
            if (y + popup.getPreferredSize().height > bounds.y + bounds.height) {
              y = (bounds.y + bounds.height) - popup.getPreferredSize().height;
            }
            popup.setLocation(x, y);
            popup.setVisible(true);
          }
        });
        try {
          SystemTray.getSystemTray().add(ti);
        } catch (AWTException ex) {
          Logger.getLogger(TestTaskIcon.class.getName()).log(Level.SEVERE, null, ex);
        }
      }
    });
  }

  public static Rectangle getSafeScreenBounds(Point pos) {

    Rectangle bounds = getScreenBoundsAt(pos);
    Insets insets = getScreenInsetsAt(pos);

    bounds.x += insets.left;
    bounds.y += insets.top;
    bounds.width -= (insets.left + insets.right);
    bounds.height -= (insets.top + insets.bottom);

    return bounds;

  }

  public static Insets getScreenInsetsAt(Point pos) {
    GraphicsDevice gd = getGraphicsDeviceAt(pos);
    Insets insets = null;
    if (gd != null) {
      insets = Toolkit.getDefaultToolkit().getScreenInsets(gd.getDefaultConfiguration());
    }
    return insets;
  }

  public static Rectangle getScreenBoundsAt(Point pos) {
    GraphicsDevice gd = getGraphicsDeviceAt(pos);
    Rectangle bounds = null;
    if (gd != null) {
      bounds = gd.getDefaultConfiguration().getBounds();
    }
    return bounds;
  }

  public static GraphicsDevice getGraphicsDeviceAt(Point pos) {

    GraphicsDevice device = null;

    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice lstGDs[] = ge.getScreenDevices();

    ArrayList<GraphicsDevice> lstDevices = new ArrayList<GraphicsDevice>(lstGDs.length);

    for (GraphicsDevice gd : lstGDs) {

      GraphicsConfiguration gc = gd.getDefaultConfiguration();
      Rectangle screenBounds = gc.getBounds();

      if (screenBounds.contains(pos)) {

        lstDevices.add(gd);

      }

    }

    if (lstDevices.size() > 0) {
      device = lstDevices.get(0);
    } else {
      device = ge.getDefaultScreenDevice();
    }

    return device;

  }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Yes, your task bar **is** the reason I asked this question. I observed from the previous question about adding `TrayIcon` that yours was on the top :) Now how do I display the `JPopup` properly ? – An SO User Jan 21 '13 at 02:01
  • This is actually demonstrated in my previous answer to you question, but I've added another example... – MadProgrammer Jan 21 '13 at 02:10
  • I fixed a bug in the positioning code where it was using the popup's width and height before it had actually begin determined, now using the preferred size instead – MadProgrammer Jan 21 '13 at 02:19
5

I think you can't determine system tray location, but you can get whole taskbar location and size. You have to use WINAPI (shell32.dll).

See this:

How do I get the taskbar's position and size?

This is example in C#, but WINAPI is available in java.

Here you can find infomation about Java + WINAPI:

Calling Win32 API method from Java

Community
  • 1
  • 1
Kamil
  • 13,363
  • 24
  • 88
  • 183
  • I thought a little bit of math calculation could do that ? In the `mouseClicked(MouseEvent e)` method, you can use the `e.getX()` and `e.getY()` to get x and y coordinates of the click – An SO User Jan 21 '13 at 01:55
  • 1
    @LittleChild I thought you asked about tray location, not mouse click location. Im not Java programmer, I can't help with that. Sorry. – Kamil Jan 21 '13 at 01:57
0

I followed the approach from Kamil to get task bar size using JNA and Win32 DLLs. Seems the mechanism works to detect task bar, but it's challanging to get exact icon position.

The JNA code, using the links provided by Kamil, implemented in Java:

package at.cone.core.tray;

import java.awt.Rectangle;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Shell32;
import com.sun.jna.platform.win32.ShellAPI.APPBARDATA;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.UINT;
import com.sun.jna.platform.win32.WinDef.UINT_PTR;

public class SystemTray {

    private static final String TASKBAR_CLASSNAME = "Shell_TrayWnd";
    private static UINT_PTR UINT_PTR_ZERO = new UINT_PTR(0);

    /*
     * See
     * https://msdn.microsoft.com/en-us/library/windows/desktop/bb762108(v=vs.85).
     * aspx
     */
    private static DWORD ABS_ALWAYSONTOP = new DWORD(0x2);
    private static DWORD ABM_GETSTATE = new DWORD(0x4);
    private static DWORD ABM_GETTASKBARPOS = new DWORD(0x5);
    private static DWORD ABM_GETAUTOHIDEBAR = new DWORD(0x7);

    static {
        System.setProperty("jna.library.path", YOUR_PATH_TO_JNA_DLLS);

    }

    private UINT position;
    private Rectangle bounds;
    private boolean alwaysOnTop;
    private boolean autoHide;

    public SystemTray() {
        HWND hTaskbar = User32.INSTANCE.FindWindow(TASKBAR_CLASSNAME, null);

        APPBARDATA data = new APPBARDATA();

        data.cbSize = new DWORD(Native.getNativeSize(APPBARDATA.class));// (uint) Marshal.SizeOf(typeof(APPBARDATA));
        data.hWnd = hTaskbar;
        UINT_PTR result = Shell32.INSTANCE.SHAppBarMessage(ABM_GETTASKBARPOS, data);
        if (result == UINT_PTR_ZERO)
            throw new IllegalStateException();

        this.position = (UINT) data.uEdge;
        this.bounds = new Rectangle(data.rc.left, data.rc.top, data.rc.right - data.rc.left,
                data.rc.bottom - data.rc.top);

        data.cbSize = new DWORD(Native.getNativeSize(APPBARDATA.class));
        result = Shell32.INSTANCE.SHAppBarMessage(ABM_GETSTATE, data);
        long state = result.longValue();
        this.alwaysOnTop = (state & ABS_ALWAYSONTOP.longValue()) == ABS_ALWAYSONTOP.longValue();
        this.autoHide = (state & ABM_GETAUTOHIDEBAR.longValue()) == ABM_GETAUTOHIDEBAR.longValue();
    }

    public Rectangle getBounds() {
        return bounds;
    }
}

But this implementation mainly considers task bar position. Exact icon position mechanism can still be improved - see https://social.msdn.microsoft.com/Forums/windows/en-US/4ac8d81e-f281-4b32-9407-e663e6c234ae/how-to-get-screen-coordinates-of-notifyicon?forum=winforms

Christoph Bimminger
  • 1,006
  • 7
  • 25