TITLE:JPopupMenuに半透明の影を付ける

Posted by aterai at 2006-07-03

JPopupMenuに半透明の影を付ける

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

  • &jar;
  • &zip;
DropShadowPopup.png

サンプルコード

class ShadowBorder extends AbstractBorder {
  private final int xoff, yoff;
  private final Insets insets;
  private BufferedImage screen = null;
  private BufferedImage shadow = null;

  public ShadowBorder(int x, int y, JComponent c, Point p) {
    this.xoff = x;
    this.yoff = y;
    this.insets = new Insets(0,0,xoff,yoff);
    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) {
      ex.printStackTrace();
    }
  }
  @Override public Insets getBorderInsets(Component c) {
    return insets;
  }
  @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.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;
    g2d.drawImage(screen, 0, 0, c);
    g2d.drawImage(shadow, 0, 0, c);
  }
}

解説

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

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

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

参考リンク

コメント

  • キャプチャーが遅いのは画面全体を撮っているからで、必要なサイズだけにすれば結構速いようです。サンプルを修正してみたところ、毎回キャプチャするようにしても特に気にならない速度で動いてます。 -- 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);
      }
      
  • メモ: 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"));
    //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();
    //  }
    //});