问题描述
好像我在 Java 中发现了一个错误:
Seems like I've found a bug in Java:
我需要创建具有透明背景的 JFrame,现在我需要为某些用户操作显示 JPopupMenu.当 JPopupMenu 完全位于 JFrame 内时,它可以正常工作.但是当 JPopupMenu 部分在 JFrame 之外时,没有项目可见.
I need to create the JFrame with a transparent background and now I need to show the JPopupMenu for some user actions. It works fine when JPopupMenu is housed fully inside a JFrame. But when the JPopupMenu is partly outside the JFrame, no item is visible.
SSCCE:
public class PopupTest { public static void main(String[] a) { final JFrame frame = new JFrame(); frame.setSize(500, 500); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createLineBorder(Color.RED)); panel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) { JPopupMenu menu = new JPopupMenu(); for (int i = 0 ; i < 10; i++) { menu.add(String.valueOf(i)); } menu.show(panel, e.getX(), e.getY()); } } }); frame.setContentPane(panel); frame.setUndecorated(true); frame.setBackground(new Color(50, 50, 50, 200)); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.setVisible(true); } }); } }
有人知道怎么解决吗?
PS: JDK 7u40,Win x64
PS: JDK 7u40, Win x64
推荐答案
这是Oracle JDK 7中的bug(顺便说一下在Open JDK 7中无法重现).
This is bug in Oracle JDK 7 (it cannot be reproduced in Open JDK 7 by the way).
要解决此问题,您可以制定一种解决方法(是的,这只是一种解决方法,不能保证它不会因某些 Java 更新而中断),以便为弹出菜单创建的窗口将变得不透明,因为它一出现,就会正确显示.最起码到现在.以下是 Java 7 及更高版本的实现方式:
To fix this you can make a workaround (yes, this is just a workaround, there is no guarantees that it won't break with some Java update) so that the window created for popup-menu will become non-opaque as soon as it shows up, then it will be displayed properly. Atleast for now. Here is how this can be done for Java version 7 and later:
PropertyChangeListener propertyChangeListener = new PropertyChangeListener () { @Override public void propertyChange ( final PropertyChangeEvent evt ) { if ( evt.getNewValue () == Boolean.TRUE ) { // Retrieving popup menu window (we won't find it if it is inside of parent frame) final Window ancestor = getWindowAncestor ( popupMenu ); if ( ancestor != null && ancestor.getClass ().getCanonicalName ().endsWith ( "HeavyWeightWindow" ) ) { // Checking that parent window for our window is opaque, only then setting opacity final Component parent = ancestor.getParent (); if ( parent != null && parent instanceof Window && parent.getBackground ().getAlpha () == 0 ) { // Making popup menu window non-opaque ancestor.setBackground ( new Color ( 0, 0, 0, 0 ) ); } } } } private Window getWindowAncestor ( Component component ) { if ( component == null ) { return null; } if ( component instanceof Window ) { return ( Window ) component; } for ( Container p = component.getParent (); p != null; p = p.getParent () ) { if ( p instanceof Window ) { return ( Window ) p; } } return null; } }; popupMenu.addPropertyChangeListener ( "visible", propertyChangeListener );
如果您还想支持 JDK 6 版本,则必须付出更多努力,因为该代码甚至无法在早期 JDK 版本上编译(在早期版本的 Window 中没有set/getBackground"方法).
You will have to put some more effort if you also want to support JDK 6- versions because that code won't even compile on earlier JDK versions (there are no "set/getBackground" methods in Window in earlier versions).