---
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 [#summary]
`Robot`で画面をキャプチャーするなどして、半透明の影を`JPopupMenu`に付けます。
#download(https://lh3.googleusercontent.com/_9Z4BYR88imo/TQTMBgsMvZI/AAAAAAAAAYg/QBh9VXR7P-I/s800/DropShadowPopup.png)
* Source Code Examples [#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));
} 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) {
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.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();
}
}
}}
* Description [#explanation]
* Description [#description]
ポップアップメニューに半透明の影をつける際、フレームからはみ出すかどうかで異なる処理を行っています。
上記のサンプルコードは、フレームからはみ出す場合に使用する`Border`クラスです。
- フレーム内
-- `JPopupMenu#paintComponent`メソッドで半透明の影を描画
- フレーム外
-- `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;
@Override public void updateUI() {
setBorder(null);
super.updateUI();
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);
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 [#reference]
- [[Menuに半透明の影を付ける>Swing/MenuWithShadow]]
* Comment [#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
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