• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JPopupMenuを半透明にする
#navi(../)
RIGHT:Posted by &author(aterai); at 2012-02-27
*JPopupMenuを半透明にする [#gcc0b988]
JPopupMenuを半透明にします。

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

//#screenshot
#ref(https://lh3.googleusercontent.com/-SKQis3B-SmY/T0dd531MovI/AAAAAAAABJk/fWIZIAeE3oE/s800/TranslucentPopupMenu.png)

**サンプルコード [#yd15f03c]
#code(link){{
class TranslucentPopupMenu extends JPopupMenu{
  private static final Color ALPHA_ZERO = new Color(0, true);
  private static final Color POPUP_BACK = new Color(250,250,250,200);
  private static final Color POPUP_LEFT = new Color(230,230,230,200);
  private static final int LEFT_WIDTH = 24;
  @Override public boolean isOpaque() {
    return false;
  }
  @Override public void updateUI() {
    super.updateUI();
    boolean isNimbus = UIManager.getBorder("PopupMenu.border")==null;
    if(isNimbus) {
      setBorder(new BorderUIResource(BorderFactory.createLineBorder(Color.GRAY)));
    }
  }
  @Override public JMenuItem add(JMenuItem menuItem) {
    menuItem.setOpaque(false);
    return super.add(menuItem);
  }
  @Override public void show(Component c, int x, int y) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        Window p = SwingUtilities.getWindowAncestor(TranslucentPopupMenu.this);
        if(p!=null && p instanceof JWindow) {
          System.out.println("Heavy weight");
          JWindow w = (JWindow)p;
          if(System.getProperty("java.version").startsWith("1.6.0")) {
            w.dispose();
            if(com.sun.awt.AWTUtilities.isWindowOpaque(w)) {
              com.sun.awt.AWTUtilities.setWindowOpaque(w, false);
            }
            w.setVisible(true);
          }else{
            w.setBackground(ALPHA_ZERO);
          }
        }else{
          System.out.println("Light weight");
        }
      }
    });
    super.show(c, x, y);
  }
  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g.create();
    g2.setPaint(POPUP_LEFT);
    g2.fillRect(0,0,LEFT_WIDTH,getHeight());
    g2.setPaint(POPUP_BACK);
    g2.fillRect(LEFT_WIDTH,0,getWidth(),getHeight());
    g2.dispose();
    //super.paintComponent(g);
  }
}
}}

**解説 [#z80cf3e7]
上記のサンプルでは、JPopupMenuは、isOpaque()メソッドをオーバーライド、JMenuItemはsetOpaque(false) として、それぞれ透明に設定し、JPopupMenu#paintComponent(...)で、半透明の背景を描画しています。

JPopupMenuが親フレームの外にはみ出す場合は、HeavyweightのJWindowを使ってJPopupMenuが表示されるので、JWindow#setBackground(new Color(0, true))で(JDK 1.6.0_10では、com.sun.awt.AWTUtilities.setWindowOpaque(w, false))JWindow自体も透明にしています。
JPopupMenuが親フレームの外にはみ出す場合は、HeavyweightのJWindowを使ってJPopupMenuが表示されるので、JWindow#setBackground(new Color(0, true))で(JDK 1.6.0_10では、com.sun.awt.AWTUtilities.setWindowOpaque(w, false))、JPopupMenu#show(...)が呼ばれるたびに、毎回(親フレームの透明度を引き継がないように?) JWindow 自体を透明にしています。

**参考リンク [#dcc353c1]
- [http://today.java.net/pub/a/today/2008/03/18/translucent-and-shaped-swing-windows.html Translucent and Shaped Swing Windows | Java.net]
- (予定)以下を別記事に移動すること!
-- メモ: PopupFactoryを継承するTranslucentPopupFactoryを作成して、PopupFactory.setSharedInstance(new TranslucentPopupFactory())と設定する方法
-- 以下、JMenuから開いたJPopupMenuも半透明にできないか、TranslucentPopupFactoryを使う方法をテスト中。
--- PopupFactory.setSharedInstance(new TranslucentPopupFactory());としないと、ルートJPopupMenuがLight weightで、JMenuのJPopupMenuがHeavy weightのときに、JMenuのJPopupMenuが半透明にならない理由が分からない…。
--- JDK 1.7.0_04, Windows 7 でテスト中
--- 上記の理由は、1.7.0_06 などで修正されている [http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7156657 Bug ID: 7156657 Version 7 doesn't support translucent popup menus against a translucent window] が原因の可能性が高い?

#code{{
//-*- mode:java; encoding:utf8n; coding:utf-8 -*-
// vim:set fileencoding=utf-8:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.*;

public class TranslucentMenuTest {
  private final JComponent tree = new JTree();
  public JComponent makeUI() {
    tree.setComponentPopupMenu(makePopupMenu());
    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(tree));
    return p;
  }
  private static JPopupMenu makePopupMenu() {
    JMenu menu = new TransparentMenu("Test");
    menu.add(new JMenuItem("Undo"));
    menu.add(new JMenuItem("Redo"));

    JPopupMenu popup = new TranslucentPopupMenu();
    popup.add(menu);
    popup.addSeparator();
    popup.add(new JMenuItem("Cut"));
    popup.add(new JMenuItem("Copy"));
    popup.add(new JMenuItem("Paste"));
    popup.add(new JMenuItem("Delete"));
    return popup;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    try{
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }catch(Exception e) {
      e.printStackTrace();
    }
    PopupFactory.setSharedInstance(new TranslucentPopupFactory());

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().add(new TranslucentMenuTest().makeUI());
    frame.setSize(320, 240);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}
//http://terai.xrea.jp/Swing/TranslucentPopupMenu.html
class TranslucentPopupMenu extends JPopupMenu {
  private static final Color ALPHA_ZERO = new Color(0, true);
  private static final Color POPUP_BACK = new Color(250,250,250,200);
  private static final Color POPUP_LEFT = new Color(230,230,230,200);
  private static final int LEFT_WIDTH = 24;
  @Override public boolean isOpaque() {
    return false;
  }
  @Override public Component add(Component c) {
    if(c instanceof JComponent) {
      ((JComponent)c).setOpaque(false);
    }
    return c;
  }
  @Override public JMenuItem add(JMenuItem menuItem) {
    menuItem.setOpaque(false);
    return super.add(menuItem);
  }
  @Override public void show(Component c, int x, int y) {
    Window p = SwingUtilities.getWindowAncestor(TranslucentPopupMenu.this);
    if(p!=null && p instanceof JWindow) {
      System.out.println("Heavy weight");
      ((JWindow)p).setBackground(ALPHA_ZERO);
    } else {
      System.out.println("Light weight");
    }
    super.show(c, x, y);
  }
  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g.create();
    g2.setPaint(POPUP_LEFT);
    g2.fillRect(0,0,LEFT_WIDTH,getHeight());
    g2.setPaint(POPUP_BACK);
    g2.fillRect(LEFT_WIDTH,0,getWidth(),getHeight());
    g2.dispose();
  }
}

class TransparentMenu extends JMenu {
  public TransparentMenu(String title) {
    super(title);
  }
  //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4688783
  private JPopupMenu popupMenu;
  private void ensurePopupMenuCreated() {
    if (popupMenu == null) {
      final JMenu thisMenu = this;
      //this.popupMenu = new JPopupMenu();
      this.popupMenu = new TranslucentPopupMenu();
      popupMenu.setOpaque(false);
      popupMenu.setInvoker(this);
      popupListener = createWinListener(popupMenu);
    }
  }
  @Override public JPopupMenu getPopupMenu() {
    ensurePopupMenuCreated();
    return popupMenu;
  }
  @Override public JMenuItem add(JMenuItem menuItem) {
    ensurePopupMenuCreated();
    menuItem.setOpaque(false);
    return popupMenu.add(menuItem);
  }
  @Override public Component add(Component c) {
    ensurePopupMenuCreated();
    if(c instanceof JComponent) {
      ((JComponent)c).setOpaque(false);
    }
    popupMenu.add(c);
    return c;
  }
  @Override public void addSeparator() {
    ensurePopupMenuCreated();
    popupMenu.addSeparator();
  }
  @Override public void insert(String s, int pos) {
    if (pos < 0) {
      throw new IllegalArgumentException("index less than zero.");
    }
    ensurePopupMenuCreated();
    popupMenu.insert(new JMenuItem(s), pos);
  }
  @Override public JMenuItem insert(JMenuItem mi, int pos) {
    if (pos < 0) {
      throw new IllegalArgumentException("index less than zero.");
    }
    ensurePopupMenuCreated();
    popupMenu.insert(mi, pos);
    return mi;
  }
  @Override public void insertSeparator(int index) {
    if (index < 0) {
      throw new IllegalArgumentException("index less than zero.");
    }
    ensurePopupMenuCreated();
    popupMenu.insert( new JPopupMenu.Separator(), index );
  }
  @Override public boolean isPopupMenuVisible() {
    ensurePopupMenuCreated();
    return popupMenu.isVisible();
  }
}

/*
<a href="http://today.java.net/pub/a/today/2008/03/18/translucent-and-shaped-swing-windows.html">
Translucent and Shaped Swing Windows | Java.net
</a>
*/
class TranslucentPopupFactory extends PopupFactory {
  @Override public Popup getPopup(Component owner, Component contents, int x, int y) throws IllegalArgumentException {
    return new TranslucentPopup(owner, contents, x, y);
  }
}

class TranslucentPopup extends Popup {
  private JWindow popupWindow;

  public TranslucentPopup(Component owner, Component contents, int ownerX, int ownerY) {
    // create a new heavyweight window
    this.popupWindow = new JWindow();
    // mark the popup with partial opacity
    //com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, (contents instanceof JToolTip) ? 0.8f : 0.95f);
    //popupWindow.setOpacity(.5f);
    popupWindow.setBackground(new Color(0, true));
    // determine the popup location
    popupWindow.setLocation(ownerX, ownerY);
    // add the contents to the popup
    popupWindow.getContentPane().add(contents);
    contents.invalidate();
    //JComponent parent = (JComponent) contents.getParent();
    // set the shadow border
    //parent.setBorder(new ShadowPopupBorder());
  }

  @Override public void show() {
    this.popupWindow.setVisible(true);
    this.popupWindow.pack();
    // mark the window as non-opaque, so that the
    // shadow border pixels take on the per-pixel
    // translucency
    //com.sun.awt.AWTUtilities.setWindowOpaque(this.popupWindow, false);
    //popupWindow.setBackground(new Color(0,0,0,0));
  }

  @Override public void hide() {
    this.popupWindow.setVisible(false);
    this.popupWindow.removeAll();
    this.popupWindow.dispose();
  }
}
}}

**コメント [#bd72dc66]
- メモ: [http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7156657 Bug ID: 7156657 Version 7 doesn't support translucent popup menus against a translucent window]、[http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/4acd0211f48b jdk8/jdk8/jdk: changeset 5453:4acd0211f48b] -- [[aterai]] &new{2012-08-10 (金) 19:22:39};
-- 1.7.0_06 で修正されている? [http://www.oracle.com/technetwork/java/javase/2col/7u6-bugfixes-1733378.html Java™ SE Development Kit 7 Update 6 Bug Fixes] -- [[aterai]] &new{2012-08-15 (水) 13:55:37};

#comment