Swing/DropShadowPopup のバックアップの現在との差分(No.8)
TITLE:JPopupMenuに半透明の影を付ける
Posted by aterai at 2006-07-03
JPopupMenuに半透明の影を付ける
Robotで画面をキャプチャーするなどして、半透明の影をJPopupMenuに付けます。- 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:
概要
Robot
で画面をキャプチャーするなどして、半透明の影をJPopupMenu
に付けます。
- &jar;
- &zip;
Screenshot
Advertisement
サンプルコード
#spanend
#spanadd
* サンプルコード [#sourcecode]
#spanend
#spanadd
#code(link){{
#spanend
class ShadowBorder extends AbstractBorder {
private final int xoff, yoff;
private final Insets insets;
private BufferedImage screen = null;
private BufferedImage shadow = 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();
screen = robot.createScreenCapture(
new Rectangle(p.x, p.y, d.width+xoff, d.height+yoff));
}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;
}
#spanadd
#spanend
@Override public Insets getBorderInsets(Component c) {
return insets;
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) {
#spanadd
#spanend
@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, 0.2f));
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);
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;
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();
}
}
解説
解説
ポップアップメニューに半透明の影をつける際、フレームからはみ出すかどうかで異なる処理を行っています。 上記のサンプルコードは、フレームからはみ出す場合に使用するBorderクラスです。 上記のサンプルコードは、フレームからはみ出す場合に使用するBorder
クラスです。
- フレーム内
- JPopupMenu#paintComponentメソッドで半透明の影を描画しています。
- フレーム内
-
JPopupMenu#paintComponent
メソッドで半透明の影を描画
-
- フレーム外
-
Robot
を使って画面全体をキャプチャーしこれを利用して半透明の影をBorder
として作成 - このためポップアップメニューがはみ出しても影を付けることが可能
-
- フレーム外
- Robotを使って画面全体をキャプチャーし、これを利用して半透明の影をBorderとして作成しています。このためポップアップメニューがはみ出しても、影を付けることができますが、多少時間が掛かります。
JDK 1.7.0 や、1.6.0_10 以上の場合は、フレーム外でもRobotを使用せず、以下のように JPopupMenu の上位Windowの背景色を透明にすることで影をつけることができます。
-
1.6.0_10
以上の場合フレーム外でもRobot
を使用せず以下のようにJPopupMenu
の上位Window
の背景色を透明にすれば影の描画が可能class DropShadowPopupMenu extends JPopupMenu { private static final int off = 4; private BufferedImage shadow = null; private Border border = null; private static final int OFFSET = 4; private final Dimension dim = new Dimension(); private transient BufferedImage shadow; #spanadd #spanend @Override public void updateUI() { setBorder(null); super.updateUI(); Border inner = getBorder(); Border outer = BorderFactory.createEmptyBorder(0, 0, OFFSET, OFFSET); setBorder(BorderFactory.createCompoundBorder(outer, inner)); } #spanadd #spanend @Override public boolean isOpaque() { return false; } @Override public void paintComponent(Graphics g) { ((Graphics2D)g).drawImage(shadow, 0, 0, this); super.paintComponent(g); #spanadd #spanend @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(); } #spanadd #spanend @Override public void show(Component c, int x, int y) { if(border==null) { Border inner = getBorder(); Border outer = BorderFactory.createEmptyBorder(0, 0, off, off); border = BorderFactory.createCompoundBorder(outer, inner); } setBorder(border); Dimension d = getPreferredSize(); int w = d.width, h = d.height; if(shadow==null || shadow.getWidth()!=w || shadow.getHeight()!=h) { 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, 0.2f)); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f)); g2.setPaint(Color.BLACK); for(int i=0;i<off;i++) { g2.fillRoundRect(off, off, w-off-off+i, h-off-off+i, 4, 4); 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(new Runnable() { @Override public void run() { Window pop = SwingUtilities.getWindowAncestor(DropShadowPopupMenu.this); if(pop instanceof JWindow) { System.out.println(pop instanceof JWindow); pop.setBackground(new Color(0,0,0,0)); //JDK 1.7.0 //com.sun.awt.AWTUtilities.setWindowOpaque(pop, false); //JDK 1.6.0_10 } 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); } }
参考リンク
参考リンク
コメント
- キャプチャーが遅いのは画面全体を撮っているからで、必要なサイズだけにすれば結構速いようです。サンプルを修正してみたところ、毎回キャプチャーするようにしても特に気にならない速度で動いてます。 -- aterai
- ソース中で
isInRootPanel
がおかしい気がするのですが・・・convertPointToScreen
がいらないのとreturn r.contains(pt)&&r.contains(p)
にしないとフレーム内の判定がおかしいようです -- sawshun- ご指摘ありがとうごさいます。
convertPointToScreen
を削除して、MyPopupMenu#isInRootPanel
は以下のように修正しました。 -- aterai
- ご指摘ありがとうごさいます。
コメント
- キャプチャーが遅いのは画面全体を撮っているからで、必要なサイズだけにすれば結構速いようです。サンプルを修正してみたところ、毎回キャプチャするようにしても特に気にならない速度で動いてます。 -- aterai
- ソース中でisInRootPanelがおかしい気がするのですが・・・
convertPointToScreenがいらないのと return r.contains(pt)&&r.contains(p) にしないとフレーム内の判定がおかしいようです -- sawshun?
- ご指摘ありがとうごさいます。convertPointToScreenを削除して、MyPopupMenu#isInRootPanelは以下のように修正しました。 -- aterai
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); }
- ご指摘ありがとうごさいます。convertPointToScreenを削除して、MyPopupMenu#isInRootPanelは以下のように修正しました。 -- aterai
- メモ: Swing - Can popup menu events be consumed by other (e.g. background) components? -- aterai
final MyPopupMenu pop = new MyPopupMenu();
pop.add(new JMenuItem("Open"));
pop.add(new JMenuItem("Save"));
pop.add(new JMenuItem("Close"));
#spandel
//pop.addSeparator(); //XXX: Nimbus
#spanend
#spanadd
// pop.addSeparator(); // XXX: Nimbus
#spanend
JSeparator s = new JSeparator();
s.setOpaque(true);
pop.add(s);
pop.add(new JMenuItem("Exit"));
JLabel label = new JLabel(icon);
label.setComponentPopupMenu(pop);
#spandel
//JDK 1.5 label.addMouseListener(new MouseAdapter() {});
#spanend
#spandel
//addMouseListener(new MouseAdapter() {
#spanend
#spandel
// public void mouseReleased(MouseEvent e) {
#spanend
#spandel
// if(e.isPopupTrigger()) {
#spanend
#spandel
// Point pt = e.getPoint();
#spanend
#spandel
// pop.show(e.getComponent(), pt.x, pt.y);
#spanend
#spandel
// }
#spanend
#spandel
// repaint();
#spanend
#spandel
// }
#spanend
#spandel
//});
#spanend
#spanadd
// JDK 1.5 label.addMouseListener(new MouseAdapter() {});
#spanend
#spanadd
// addMouseListener(new MouseAdapter() {
#spanend
#spanadd
// public void mouseReleased(MouseEvent e) {
#spanend
#spanadd
// if (e.isPopupTrigger()) {
#spanend
#spanadd
// Point pt = e.getPoint();
#spanend
#spanadd
// pop.show(e.getComponent(), pt.x, pt.y);
#spanend
#spanadd
// }
#spanend
#spanadd
// repaint();
#spanend
#spanadd
// }
#spanend
#spanadd
// });
#spanend
- Synth(Nimbus) LnF で、JSeparator だけでなく JMenuItem まで透明になった修正?に対応。 -- aterai
-
SynthLookAndFeel
(Nimbus
など)で、JSeparator
だけでなくJMenuItem
まで透明になった修正?に対応。 -- aterai -
1.7.0_03
でなにか変更されたのか変な挙動をするようになったので、調査中。 -- aterai -
exit
やclose
が動作するのかと思ったのですが動かないんですよね?JPopupMenu
に表示させているだけでしょうか、もしそうならExit
を押したときにフレームが終了するようなコードはどう書けばいいのでしょうか? -- hshs- 影を付けるだけのサンプルコードなので、各
JMenuItem
のアクションは空になっています。「フレームを終了するコード…」は、複数のJFrame
が開いているかもしれない場合を考慮して、以下のような方法を使用するのがいいかもしれません。 -- aterai
- 影を付けるだけのサンプルコードなので、各
#spanend
#spanadd
JMenuItem mi = new JMenuItem(new AbstractAction("Exit") {
#spanend
@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();
}
#spanadd
});
#spanend
#spanadd
- 返信ありがとうございます、当方
NetBeans
で開発してまして、上記のコードをjPopupMenu1.add(この中);
にnew JMenuItem
以降を入れたのですが動きませんでした。よってJMenuItem m
~f.dispose();
までを削除し、かわりにjFrame1.setVisible(false);
を入れると動作しました。 -- hshs- メモ: せっかくなので?、JPopupMenuなどからWindowを閉じるを作成してみました。 -- aterai