概要

TrayIconをクリックしてJPopupMenuを表示します。

サンプルコード

SystemTray tray  = SystemTray.getSystemTray();
Image image = new ImageIcon(getClass().getResource("16x16.png")).getImage();
TrayIcon icon = new TrayIcon(image, "TRAY", null);
JPopupMenu popup = new JPopupMenu();
JDialog tmp = new JDialog();
tmp.setUndecorated(true);
popup.addPopupMenuListener(new PopupMenuListener() {
  @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    /* not needed */
  }

  @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    tmp.setVisible(false);
  }

  @Override public void popupMenuCanceled(PopupMenuEvent e) {
    tmp.setVisible(false);
  }
});

icon.addMouseListener(new MouseAdapter() {
  private void showJPopupMenu(MouseEvent e) {
    if (e.isPopupTrigger()) {
      Point p = adjustPopupLocation(popup, e.getX(), e.getY());
      tmp.setLocation(p);
      tmp.setVisible(true);
      popup.show(tmp, 0, 0);
    }
  }

  @Override public void mouseReleased(MouseEvent e) {
    showJPopupMenu(e);
  }

  @Override public void mousePressed(MouseEvent e) {
    showJPopupMenu(e);
  }
});
View in GitHub: Java, Kotlin

解説

JDK 1.6.0TrayIconjava.awt.PopupMenuのみ設定可能でjavax.swing.JPopupMenuは使用不可になっています。そのため上記のサンプルでは、装飾なし(setUndecorated(true))でサイズが0x0JDialogを適当な位置(TrayIconのクリックでJPopupMenuが開いたように見える場所)に配置し、これを親にしてjavax.swing.JPopupMenuを表示しています。

  • PopupMenuではなくJPopupMenuが使用できるので以下が可能
    • JCheckBoxMenuItemJRadioButtonMenuItemの使用
    • LookAndFeelの変更
  • このサンプルでは、JPopupMenu#adjustPopupLocationToFitScreen(...)メソッドを改変してSystemTrayの位置によってJPopupMenuが画面外にはみ出さないように調整
// Copied from JPopupMenu.java: JPopupMenu#adjustPopupLocationToFitScreen(...)
private static Point adjustPopupLocation(
    JPopupMenu popup, int xposition, int yposition) {
  Point p = new Point(xposition, yposition);
  if (GraphicsEnvironment.isHeadless()) {
    return p;
  }
  Rectangle screenBounds;
  GraphicsConfiguration gc = null;
  // Try to find GraphicsConfiguration, that includes mouse pointer position
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  for (GraphicsDevice gd : ge.getScreenDevices()) {
    if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
      GraphicsConfiguration dgc = gd.getDefaultConfiguration();
      if (dgc.getBounds().contains(p)) {
        gc = dgc;
        break;
      }
    }
  }

  // If not found and popup have invoker, ask invoker about his gc
  if (gc == null && popup.getInvoker() != null) {
    gc = popup.getInvoker().getGraphicsConfiguration();
  }

  if (gc != null) {
    // If we have GraphicsConfiguration use it to get
    // screen bounds
    screenBounds = gc.getBounds();
  } else {
    // If we don't have GraphicsConfiguration use primary screen
    screenBounds = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
  }

  Dimension size = popup.getPreferredSize();

  // Use long variables to prevent overflow
  long pw = (long) p.x + (long) size.width;
  long ph = (long) p.y + (long) size.height;

  if (pw > screenBounds.x + screenBounds.width) {
    p.x -= size.width;
  }
  if (ph > screenBounds.y + screenBounds.height) {
    p.y -= size.height;
  }

  // Change is made to the desired (X, Y) values, when the
  // PopupMenu is too tall OR too wide for the screen
  p.x = Math.max(p.x, screenBounds.x);
  p.y = Math.max(p.y, screenBounds.y);
  return p;
}

参考リンク

コメント