4

好像我在 Java 中发现了一个错误:

我需要创建JFrame一个透明背景,现在我需要显示JPopupMenu一些用户操作。当JPopupMenu完全容纳在JFrame. 但是当JPopupMenu部分在 之外时JFrame,没有项目可见。

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

4

3 回答 3

4

这是 Oracle JDK 7 中的错误(顺便说一下,它无法在 Open JDK 7 中重现)。

要解决此问题,您可以采取一种解决方法(是的,这只是一种解决方法,不能保证它不会因某些 Java 更新而中断),以便为弹出菜单创建的窗口一旦它就变得不透明出现,然后它将正确显示。至少现在。以下是对 Java 7 及更高版本执行此操作的方法:

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”方法)。

于 2013-09-20T14:00:41.070 回答
1

.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Painter;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class PopupTest {

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel(new BorderLayout()) {
        private static final long serialVersionUID = 1L;

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 500);
        }
    };
    private JButton button = new JButton("Close me");

    public PopupTest() {
        //panel.setOpaque(false);        
        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 < 56; i++) {//only FHD display
                        menu.add(String.valueOf(i));
                    }
                    menu.show(panel, e.getX(), e.getY());
                }
            }
        });
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        button.setOpaque(false);
        panel.add(button, BorderLayout.SOUTH);
        frame.setLocation(150, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //frame.add(panel);
        frame.setContentPane(panel);
        frame.setUndecorated(true);
        frame.pack();
        frame.setBackground(new Color(150, 50, 50, 200));
        frame.setVisible(true);
    }

    public static void main(String[] a) {
        try {
            for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(laf.getName())) {
                    UIManager.setLookAndFeel(laf.getClassName());
                    UIManager.getLookAndFeelDefaults().put("PopupMenu[Enabled].backgroundPainter",
                            new FillPainter(new Color(127, 255, 191)));
                    UIManager.getLookAndFeelDefaults().put("text", new Color(255, 0, 0));
                    //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                    //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PopupTest();
            }
        });
    }
}

class FillPainter implements Painter<JComponent> {

    private final Color color;

    FillPainter(Color c) {
        color = c;
    }

    @Override
    public void paint(Graphics2D g, JComponent object, int width, int height) {
        g.setColor(color);
        g.fillRect(0, 0, width - 1, height - 1);
    }
}
于 2013-09-20T21:57:00.277 回答
0

感谢 Mikle Garin 提供了一个很好的解决方案,你帮我解决了类似的问题!我想分享我的解决方案——基于 Mikle 的——有一点不同,这对我来说很重要。

我在找什么:JPopupMenu 后面的透明未装饰窗口(我的自定义弹出窗口显示有花哨的语音气球边框,所以它后面的窗口应该是不可见的)。

PropertyChangeListener 不能很好地工作的一件事是:在屏幕上显示窗口后调整窗口外观。在 Mac OS X 10.10 上,弹出窗口后面带有 java 8 窗口首先显示为白色背景和细边框(L&F 默认),稍后会调整一段时间(大约 0.1 - 0.3 秒)。很烦人。

在查看了在哪里放置调整代码后,我想出了以下简单的解决方案:在菜单添加到窗口之后和显示窗口之前调整窗口。以下示例显示了如何扩展 JPopupMenu 以实现此目的:

import java.awt.Color;
import java.awt.Window;
import javax.swing.JPopupMenu;
import javax.swing.Popup;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.plaf.PopupMenuUI;

public class MyPopup extends JPopupMenu
{
    public MyPopup()
    {
        super();
        //...
        setUI(new MyPopupMenuUI());
    }

    //...

    private void adjustHeavyWeightWindowIfThereIsOne()
    {
        // Retrieve popup menu window
        // on Windows we won't find it if this popup is inside of parent frame
        // on Mac we may find it even when DefaultLightWeightPopupEnabled is set to true
        final Window ancestor = SwingUtilities.getWindowAncestor(MyPopup.this);
        if (ancestor != null && ancestor.getClass().getCanonicalName().endsWith("HeavyWeightWindow"))
        {
            adjustWindowAppearance(ancestor);
        }
    }

    private void adjustWindowAppearance(Window w)
    {
        w.setBackground(new Color(0, 0, 0, 0));
        if (w instanceof RootPaneContainer)
        {
            ((RootPaneContainer)w).getRootPane().setBorder(null);
            ((RootPaneContainer)w).getRootPane().setBackground(new Color(0, 0, 0, 0));
        }
    }

    class MyPopupMenuUI extends PopupMenuUI
    {
        public Popup getPopup(JPopupMenu popup, int x, int y)
        {
            Popup toreturn = super.getPopup(popup, x, y);
            adjustHeavyWeightWindowIfThereIsOne();
            return toreturn;
        }
    }
}

实际上扩展 JPopupMenu 是没有必要的,但是设置一个触发调整的自定义 PopupMenuUI 是必不可少的。窗口调整代码可以轻松修改以满足特定需求,并且可以移动到自定义 PopupMenuUI。

于 2015-06-30T11:23:02.500 回答