• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: DropShadowPopup
title: JPopupMenuに半透明の影を付ける
tags: [JPopupMenu, Border, Robot, JMenuItem, Translucent]
author: aterai
pubdate: 2006-07-03T12:55:36+09:00
description: Robotで画面をキャプチャーするなどして、半透明の影をJPopupMenuに付けます。
image: https://lh3.googleusercontent.com/_9Z4BYR88imo/TQTMBgsMvZI/AAAAAAAAAYg/QBh9VXR7P-I/s800/DropShadowPopup.png
---
* 概要 [#summary]
`Robot`で画面をキャプチャーするなどして、半透明の影を`JPopupMenu`に付けます。

#download(https://lh3.googleusercontent.com/_9Z4BYR88imo/TQTMBgsMvZI/AAAAAAAAAYg/QBh9VXR7P-I/s800/DropShadowPopup.png)

* サンプルコード [#sourcecode]
#code(link){{
class ShadowBorder extends AbstractBorder {
  private final int xoff, yoff;
  private final transient BufferedImage screen;
  private transient BufferedImage shadow;

  public ShadowBorder(int x, int y, JComponent c, Point p) {
    super();
    this.xoff = x;
    this.yoff = y;
    BufferedImage bi = null;
    try {
      Robot robot = new Robot();
      Dimension d = c.getPreferredSize();
      bi = robot.createScreenCapture(new Rectangle(p.x, p.y, d.width + xoff, d.height + yoff));
      bi = robot.createScreenCapture(
          new Rectangle(p.x, p.y, d.width + xoff, d.height + yoff));
    } catch (AWTException ex) {
      ex.printStackTrace();
    }
    screen = bi;
  }

  @Override public Insets getBorderInsets(Component c) {
    return new Insets(0, 0, xoff, yoff);
  }
  @Override public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {

  @Override public void paintBorder(
        Component c, Graphics g, int x, int y, int w, int h) {
    if (screen == null) {
      return;
    }
    if (shadow == null || shadow.getWidth() != w || shadow.getHeight() != h) {
      shadow = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = shadow.createGraphics();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f));
      g2.setPaint(Color.BLACK);
      for (int i = 0; i < xoff; i++) {
        g2.fillRoundRect(xoff, xoff, w - xoff - xoff + i, h - xoff - xoff + i, 4, 4);
      }
      g2.dispose();
    }
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.drawImage(screen, 0, 0, c);
    g2d.drawImage(shadow, 0, 0, c);
    g2d.setPaint(c.getBackground()); //??? 1.7.0_03
    g2d.fillRect(x, y, w - xoff, h - yoff);
    g2d.dispose();
  }
}
}}

* 解説 [#explanation]
ポップアップメニューに半透明の影をつける際、フレームからはみ出すかどうかで異なる処理を行っています。

上記のサンプルコードは、フレームからはみ出す場合に使用する`Border`クラスです。

- フレーム内
-- `JPopupMenu#paintComponent`メソッドで半透明の影を描画
- フレーム外
-- `Robot`を使って画面全体をキャプチャーし、これを利用して半透明の影を`Border`として作成
-- このためポップアップメニューがはみ出しても、影を付けることが可能
-- `Robot`を使って画面全体をキャプチャーしこれを利用して半透明の影を`Border`として作成
-- このためポップアップメニューがはみ出しても影を付けることが可能

----
`JDK 1.7.0`や、`1.6.0_10`以上の場合は、フレーム外でも`Robot`を使用せず、以下のように`JPopupMenu`の上位`Window`の背景色を透明にすることで影をつけることができます。

- `1.6.0_10`以上の場合フレーム外でも`Robot`を使用せず以下のように`JPopupMenu`の上位`Window`の背景色を透明にすれば影の描画が可能
#code{{
class DropShadowPopupMenu extends JPopupMenu {
  private static final int OFFSET = 4;
  private final Dimension dim = new Dimension();
  private transient BufferedImage shadow;
  private Border border;
  @Override public boolean isOpaque() {
    return false;
  }

  @Override public void updateUI() {
    setBorder(null);
    super.updateUI();
    border = null;
    Border inner = getBorder();
    Border outer = BorderFactory.createEmptyBorder(0, 0, OFFSET, OFFSET);
    setBorder(BorderFactory.createCompoundBorder(outer, inner));
  }

  @Override public boolean isOpaque() {
    return false;
  }

  @Override protected void paintComponent(Graphics g) {
    //super.paintComponent(g);
    // super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.drawImage(shadow, 0, 0, this);
    g2.setPaint(getBackground()); //??? 1.7.0_03
    g2.setPaint(getBackground()); // ??? 1.7.0_03
    g2.fillRect(0, 0, getWidth() - OFFSET, getHeight() - OFFSET);
    g2.dispose();
  }

  @Override public void show(Component c, int x, int y) {
    if (border == null) {
      Border inner = getBorder();
      Border outer = BorderFactory.createEmptyBorder(0, 0, OFFSET, OFFSET);
      border = BorderFactory.createCompoundBorder(outer, inner);
    }
    setBorder(border);
    Dimension d = getPreferredSize();
    int w = d.width;
    int h = d.height;
    if (shadow == null || shadow.getWidth() != w || shadow.getHeight() != h) {
    if (dim.width != w || dim.height != h) {
      dim.setSize(w, h);
      shadow = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = shadow.createGraphics();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f));
      g2.setPaint(Color.BLACK);
      for (int i = 0; i < OFFSET; i++) {
        g2.fillRoundRect(
            OFFSET, OFFSET, w - OFFSET - OFFSET + i, h - OFFSET - OFFSET + i, 4, 4);
            OFFSET, OFFSET, w - OFFSET - OFFSET + i,
            h - OFFSET - OFFSET + i, 4, 4);
      }
      g2.dispose();
    }
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        Window pop = SwingUtilities.getWindowAncestor(DropShadowPopupMenu.this);
        if (pop instanceof JWindow) {
          pop.setBackground(new Color(0x0, true)); //JDK 1.7.0
        }
    EventQueue.invokeLater(() -> {
      Window top = SwingUtilities.getWindowAncestor(this);
      if (top != null && top.getType() == Window.Type.POPUP) {
        // Popup$HeavyWeightWindow
        top.setBackground(new Color(0x0, true)); // JDK 1.7.0
      }
    });
    super.show(c, x, y);
  }
}
}}

* 参考リンク [#reference]
- [[Menuに半透明の影を付ける>Swing/MenuWithShadow]]

* コメント [#comment]
#comment
- キャプチャーが遅いのは画面全体を撮っているからで、必要なサイズだけにすれば結構速いようです。サンプルを修正してみたところ、毎回キャプチャーするようにしても特に気にならない速度で動いてます。 -- &user(aterai); &new{2006-07-18 (火) 12:02:13};
- ソース中で`isInRootPanel`がおかしい気がするのですが・・・
//convertPointToScreenga -- &user(sawshun); &new{2006-10-05 (木) 11:20:25};
//- #改行しようとしたらコメント入ってしまいました
// コメント勝手に修正しました(terai)
`convertPointToScreen`がいらないのと`return r.contains(pt)&&r.contains(p)`にしないとフレーム内の判定がおかしいようです -- &user(sawshun); &new{2006-10-05 (木) 11:23:39};
-- ご指摘ありがとうごさいます。`convertPointToScreen`を削除して、`MyPopupMenu#isInRootPanel`は以下のように修正しました。 -- &user(aterai); &new{2006-10-05 (木) 12:34:12};

#code{{
private boolean isInRootPanel(JComponent root, Point p) {
  Rectangle r = root.getBounds();
  Dimension d = this.getPreferredSize();
  //pointed out by sawshun
  // pointed out by sawshun
  return r.contains(p.x, p.y, d.width + off, d.height + off);
}
}}

- メモ: [https://community.oracle.com/thread/1393754 Swing - Can popup menu events be consumed by other (e.g. background) components?] -- &user(aterai); &new{2008-04-10 (木) 18:24:18};

#code{{
final MyPopupMenu pop = new MyPopupMenu();
pop.add(new JMenuItem("Open"));
pop.add(new JMenuItem("Save"));
pop.add(new JMenuItem("Close"));
//pop.addSeparator(); //XXX: Nimbus
// pop.addSeparator(); // XXX: Nimbus
JSeparator s = new JSeparator();
s.setOpaque(true);
pop.add(s);
pop.add(new JMenuItem("Exit"));
JLabel label = new JLabel(icon);
label.setComponentPopupMenu(pop);
//JDK 1.5 label.addMouseListener(new MouseAdapter() {});
//addMouseListener(new MouseAdapter() {
//  public void mouseReleased(MouseEvent e) {
//    if (e.isPopupTrigger()) {
//      Point pt = e.getPoint();
//      pop.show(e.getComponent(), pt.x, pt.y);
//    }
//    repaint();
//  }
//});
// JDK 1.5 label.addMouseListener(new MouseAdapter() {});
// addMouseListener(new MouseAdapter() {
//   public void mouseReleased(MouseEvent e) {
//     if (e.isPopupTrigger()) {
//       Point pt = e.getPoint();
//       pop.show(e.getComponent(), pt.x, pt.y);
//     }
//     repaint();
//   }
// });
}}
- `SynthLookAndFeel`(`Nimbus`など)で、`JSeparator`だけでなく`JMenuItem`まで透明になった修正?に対応。 -- &user(aterai); &new{2012-02-05 (日) 14:22:34};
- `1.7.0_03`でなにか変更があった?のか、変な挙動をするようになったので、調査中。 -- &user(aterai); &new{2012-02-21 (火) 16:45:48};
- `1.7.0_03`でなにか変更されたのか変な挙動をするようになったので、調査中。 -- &user(aterai); &new{2012-02-21 (火) 16:45:48};
- `exit`や`close`が動作するのかと思ったのですが動かないんですよね? `JPopupMenu`に表示させているだけでしょうか、もしそうなら`Exit`を押したときにフレームが終了するようなコードはどう書けばいいのでしょうか? -- &user(hshs); &new{2013-03-02 (土) 05:32:25};
-- 影を付けるだけのサンプルコードなので、`JMenuItem`は名前だけのダミーになっています。「フレームを終了するコード…」は、複数の`JFrame`が開いているかもしれない場合を考慮して、以下のような方法を使用するのがいいかもしれません。 -- &user(aterai); &new{2013-03-04 (月) 09:53:00};
-- 影を付けるだけのサンプルコードなので、各`JMenuItem`のアクションは空になっています。「フレームを終了するコード…」は、複数の`JFrame`が開いているかもしれない場合を考慮して、以下のような方法を使用するのがいいかもしれません。 -- &user(aterai); &new{2013-03-04 (月) 09:53:00};

#code{{
JMenuItem mi = new JMenuItem(new AbstractAction("Exit") {
  @Override public void actionPerformed(ActionEvent e) {
    JMenuItem m = (JMenuItem) e.getSource();
    JPopupMenu popup = (JPopupMenu) m.getParent();
    JComponent invoker = (JComponent) popup.getInvoker();
    Window f = SwingUtilities.getWindowAncestor(invoker);
    if (f != null) f.dispose();
  }
});
}}
- 返信ありがとうございます、当方`Netbeans`で開発してまして、上記のコードを`jPopupMenu1.add(この中);`に`new JMenuItem`以降を入れたのですが動きませんでした。よって`JMenuItem m~f.dispose();`までを削除し、かわりに`jFrame1.setVisible(false);`を入れると動作しました。 -- &user(hshs); &new{2013-03-05 (火) 20:22:26};
- 返信ありがとうございます、当方`NetBeans`で開発してまして、上記のコードを`jPopupMenu1.add(この中);`に`new JMenuItem`以降を入れたのですが動きませんでした。よって`JMenuItem m`~`f.dispose();`までを削除し、かわりに`jFrame1.setVisible(false);`を入れると動作しました。 -- &user(hshs); &new{2013-03-05 (火) 20:22:26};
-- メモ: せっかくなので?、[[JPopupMenuなどからWindowを閉じる>Swing/WindowClosingAction]]を作成してみました。 -- &user(aterai); &new{2013-03-11 (月) 17:09:34};

#comment