• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JPopupMenuに半透明の影を付ける
#navi(../)
*JPopupMenuに半透明の影を付ける [#z6537cfc]
Posted by [[terai]] at 2006-07-03
---
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`に付けます。

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

**概要 [#qe9dac90]
Robotで画面をキャプチャーするなどして、半透明の影をJPopupMenuに付けます。

//-&jnlp;
-&jar;
-&zip;

#screenshot

**サンプルコード [#q20cf4f0]
#code{{
* サンプルコード [#sourcecode]
#code(link){{
class ShadowBorder extends AbstractBorder {
  private final int xoff, yoff;
  private final Insets insets;
  protected BufferedImage screenShot = null;
  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;
    this.insets = new Insets(0,0,xoff,yoff);
    try{
    BufferedImage bi = null;
    try {
      Robot robot = new Robot();
      Dimension d = c.getPreferredSize();
      Rectangle rect = new Rectangle(p.x, p.y, d.width+x, d.height+y);
      screenShot = robot.createScreenCapture(rect);
    }catch (java.awt.AWTException ex) {}
      bi = robot.createScreenCapture(
          new Rectangle(p.x, p.y, d.width + xoff, d.height + yoff));
    } catch (AWTException ex) {
      ex.printStackTrace();
    }
    screen = bi;
  }
  public Insets getBorderInsets(Component c) {
    return insets;

  @Override public Insets getBorderInsets(Component c) {
    return new Insets(0, 0, xoff, yoff);
  }
  public void paintBorder(Component comp, Graphics g,
                          int x, int y, int w, int h) {
    if(screenShot==null) return;
    BufferedImage 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,0.2f));
    g2.setPaint(Color.BLACK);
    for(int i=0;i<xoff;i++) {
      g2.fillRoundRect(xoff, xoff, w-xoff-xoff+i, h-xoff-xoff+i, 10,10);

  @Override public void paintBorder(
        Component c, Graphics g, int x, int y, int w, int h) {
    if (screen == null) {
      return;
    }
    g2.dispose();
    Graphics2D gx = (Graphics2D) g;
    gx.drawImage(screenShot, 0, 0, comp);
    gx.drawImage(shadow, 0, 0, comp);
    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.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();
  }
}
}}

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

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

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

-フレーム外
--Robotを使って画面全体をキャプチャーし、これを利用して半透明の影をBorderとして作成しています。このためポップアップメニューがはみ出しても、影を付けることができますが、多少時間が掛かります。
----
- `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;

**参考リンク [#jb8cc361]
-[[Menuに半透明の影を付ける>Swing/MenuWithShadow]]
  @Override public void updateUI() {
    setBorder(null);
    super.updateUI();
    Border inner = getBorder();
    Border outer = BorderFactory.createEmptyBorder(0, 0, OFFSET, OFFSET);
    setBorder(BorderFactory.createCompoundBorder(outer, inner));
  }

**コメント [#e88a4825]
- キャプチャーが遅いのは画面全体を撮っているからで、必要なサイズだけにすれば結構速いようです。サンプルを修正してみたところ、毎回キャプチャするようにしても特に気にならない速度で動いてます。 -- [[terai]] &new{2006-07-18 (火) 12:02:13};
- ソース中でisInRootPanelがおかしい気がするのですが・・・ 
//convertPointToScreenga -- [[sawshun]] &new{2006-10-05 (木) 11:20:25};
//- #改行しようとしたらコメント入ってしまいました  
  @Override public boolean isOpaque() {
    return false;
  }

  @Override protected void paintComponent(Graphics g) {
    // super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.drawImage(shadow, 0, 0, this);
    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) {
    Dimension d = getPreferredSize();
    int w = d.width;
    int h = d.height;
    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);
      }
      g2.dispose();
    }
    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) にしないとフレーム内の判定がおかしいようです -- [[sawshun]] &new{2006-10-05 (木) 11:23:39};
-- ご指摘ありがとうごさいます。convertPointToScreenを削除して、MyPopupMenu#isInRootPanelは以下のように修正しました。 -- [[terai]] &new{2006-10-05 (木) 12:34:12};
`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
  return r.contains(p.x, p.y, d.width+off, d.height+off);
  // 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
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();
//   }
// });
}}
- `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};
- `exit`や`close`が動作するのかと思ったのですが動かないんですよね? `JPopupMenu`に表示させているだけでしょうか、もしそうなら`Exit`を押したときにフレームが終了するようなコードはどう書けばいいのでしょうか? -- &user(hshs); &new{2013-03-02 (土) 05:32:25};
-- 影を付けるだけのサンプルコードなので、各`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};
-- メモ: せっかくなので?、[[JPopupMenuなどからWindowを閉じる>Swing/WindowClosingAction]]を作成してみました。 -- &user(aterai); &new{2013-03-11 (月) 17:09:34};

#comment