• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:TrayIconでJPopupMenuを使用する
#navi(../)
RIGHT:Posted by [[aterai]] at 2011-01-31
*TrayIconでJPopupMenuを使用する [#kc07ccfd]
TrayIconをクリックしてJPopupMenuを表示します。
---
category: swing
folder: TrayIconPopupMenu
title: TrayIconでJPopupMenuを使用する
tags: [TrayIcon, JPopupMenu, JDialog, LookAndFeel, JCheckBoxMenuItem, JRadioButtonMenuItem]
author: aterai
pubdate: 2011-01-31T15:26:03+09:00
description: TrayIconをクリックしてJPopupMenuを表示します。
image: https://lh5.googleusercontent.com/_9Z4BYR88imo/TUZUBCgOGJI/AAAAAAAAA0A/Ox5g3HoxmoI/s800/TrayIconPopupMenu.png
---
* 概要 [#summary]
`TrayIcon`をクリックして`JPopupMenu`を表示します。

//-&jnlp;
-&jar;
-&zip;
#download(https://lh5.googleusercontent.com/_9Z4BYR88imo/TUZUBCgOGJI/AAAAAAAAA0A/Ox5g3HoxmoI/s800/TrayIconPopupMenu.png)

//#screenshot
#ref(http://lh5.ggpht.com/_9Z4BYR88imo/TUZUBCgOGJI/AAAAAAAAA0A/Ox5g3HoxmoI/s800/TrayIconPopupMenu.png)
* サンプルコード [#sourcecode]
#code(link){{
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 */
  }

**サンプルコード [#q69216fd]
#code{{
final SystemTray tray  = SystemTray.getSystemTray();
final Image image      = new ImageIcon(getClass().getResource("16x16.png")).getImage();
final TrayIcon icon    = new TrayIcon(image, "TRAY", null);
final JPopupMenu popup = new JPopupMenu();
final JDialog dummy    = new JDialog();
dummy.setUndecorated(true);
popup.addPopupMenuListener(new PopupMenuListener() {
  @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
  @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    dummy.setVisible(false);
    tmp.setVisible(false);
  }

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

icon.addMouseListener(new MouseAdapter() {
  private void showJPopupMenu(MouseEvent e) {
    if(e.isPopupTrigger()) {
    if (e.isPopupTrigger()) {
      Point p = adjustPopupLocation(popup, e.getX(), e.getY());
      dummy.setLocation(p);
      dummy.setVisible(true);
      popup.show(dummy, 0, 0);
      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);
  }
});
}}

**解説 [#sa6566ea]
Java 6 では、TrayIconにはjava.awt.PopupMenuしか使用できないので、setUndecorated(true)かつ、サイズが0x0のJDialogを適当な位置(TrayIconのクリックでJPopupMenuが開いたように見える場所)に配置し、これを親にしてjavax.swing.JPopupMenuを表示しています。
* 解説 [#explanation]
`JDK 1.6.0`の`TrayIcon`は`java.awt.PopupMenu`のみ設定可能で`javax.swing.JPopupMenu`は使用不可になっています。そのため上記のサンプルでは、装飾なし(`setUndecorated(true)`)でサイズが`0x0`の`JDialog`を適当な位置(`TrayIcon`のクリックで`JPopupMenu`が開いたように見える場所)に配置し、これを親にして`javax.swing.JPopupMenu`を表示しています。

----
PopupMenuではなく、JPopupMenuが使用できるので、以下のようなことが可能になります。
- JCheckBoxMenuItem、JRadioButtonMenuItemの使用
- LookAndFeelの変更
- `PopupMenu`ではなく`JPopupMenu`が使用できるので以下が可能
-- `JCheckBoxMenuItem`、`JRadioButtonMenuItem`の使用
-- `LookAndFeel`の変更
- このサンプルでは、`JPopupMenu#adjustPopupLocationToFitScreen(...)`メソッドを改変して`SystemTray`の位置によって`JPopupMenu`が画面外にはみ出さないように調整

----
このサンプルでは、JPopupMenu#adjustPopupLocationToFitScreen(...)メソッドを改変して、SystemTrayの位置によってJPopupMenuが画面外にはみ出さないように調整しています。

#code{{
//Copied from JPopupMenu.java
private static Point adjustPopupLocation(JPopupMenu popup, int xposition, int yposition) {
// 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;

  if (GraphicsEnvironment.isHeadless()) {
    return p;
  }
  Rectangle screenBounds;
  GraphicsConfiguration gc = null;
  // Try to find GraphicsConfiguration, that includes mouse pointer position
  for (GraphicsDevice gd: GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
  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;
  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
  // Change is made to the desired (X, Y) values, when the
  // PopupMenu is too tall OR too wide for the screen
  if (p.x < screenBounds.x) p.x = screenBounds.x;
  if (p.y < screenBounds.y) p.y = screenBounds.y;

  p.x = Math.max(p.x, screenBounds.x);
  p.y = Math.max(p.y, screenBounds.y);
  return p;
}
}}

**参考リンク [#y7e86d22]
- [http://download.oracle.com/javase/tutorial/uiswing/misc/systemtray.html How to Use the System Tray (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features)]
- [http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6285881 Bug ID: 6285881 JTrayIcon: support Swing JPopupMenus for tray icons]
- [http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html JTrayIcon update | Java.net]
- [http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6453521 Bug ID: 6453521 TrayIcon should support transparency]
* 参考リンク [#reference]
- [https://web.archive.org/web/20090327054056/http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html Alexander Potochkin's Blog: JTrayIcon update]
-- [https://github.com/floscher/swinghelper/blob/master/src/java/org/jdesktop/swinghelper/tray/JXTrayIcon.java swinghelper/JXTrayIcon.java at master · floscher/swinghelper · GitHub]
- [https://docs.oracle.com/javase/tutorial/uiswing/misc/systemtray.html How to Use the System Tray (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features)]
- [https://bugs.openjdk.org/browse/JDK-6285881 Bug ID: 6285881 JTrayIcon: support Swing JPopupMenus for tray icons]
- [https://bugs.openjdk.org/browse/JDK-6453521 Bug ID: 6453521 TrayIcon should support transparency]

**コメント [#kcb0edde]
- example.jarとsrc.zipを上げ忘れていたのを修正orz。 -- [[aterai]] &new{2011-02-02 (水) 19:07:51};
* コメント [#comment]
#comment
- ソースを上げ忘れていたのを修正。 -- &user(aterai); &new{2011-02-02 (水) 19:07:51};
- `JRE1.6.0u3`で`2`度連続で右クリックすると`ClassCastException`起きちゃうんですよね・・・`BugParade`でも見つけらんなかったです -- &user(sawshun); &new{2011-10-25 (火) 18:45:38};
-- どうもです。こちらでも`WindowsXP`+`Java6u3`の環境で、`TrayIcon`上で右クリックを繰り返すと、`ClassCastException: java.awt.TrayIcon cannot be cast to java.awt.Component`が発生するのを確認しました。`bugs.java.com`を調べたら、`6u10`で修正された [https://bugs.openjdk.org/browse/JDK-6583251 Bug ID: 6583251 One more ClassCastException in Swing with TrayIcon]がそれっぽい気がします。 -- &user(aterai); &new{2011-10-26 (水) 00:56:41};
- 情報ありがとうございます・・・`u10`か・・・`SynthUI`がらみの大きなパッケージ変更がイヤで古代の`Ver`を利用しているのでちょっと工夫してみます -- &user(sawshun); &new{2011-10-27 (木) 10:45:59};

#comment