Swing/DropShadowPopup の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/DropShadowPopup へ行く。
- Swing/DropShadowPopup の差分を削除
--- 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)); } 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(); } } }} * 解説 [#explanation] ポップアップメニューに半透明の影をつける際、フレームからはみ出すかどうかで異なる処理を行っています。 上記のサンプルコードは、フレームからはみ出す場合に使用する`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] - [[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 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}; -- 影を付けるだけのサンプルコードなので、各`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